1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2012, 2013 Zimbra Software, LLC. 5 * 6 * The contents of this file are subject to the Zimbra Public License 7 * Version 1.4 ("License"); you may not use this file except in 8 * compliance with the License. You may obtain a copy of the License at 9 * http://www.zimbra.com/license. 10 * 11 * Software distributed under the License is distributed on an "AS IS" 12 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. 13 * ***** END LICENSE BLOCK ***** 14 */ 15 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY 16 (function(win) { 17 var whiteSpaceRe = /^\s*|\s*$/g, 18 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; 19 20 var tinymce = { 21 majorVersion : '3', 22 23 minorVersion : '5.4.1', 24 25 releaseDate : '2012-06-24', 26 27 _init : function() { 28 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; 29 30 t.isOpera = win.opera && opera.buildNumber; 31 32 t.isWebKit = /WebKit/.test(ua); 33 34 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); 35 36 t.isIE6 = t.isIE && /MSIE [56]/.test(ua); 37 38 t.isIE7 = t.isIE && /MSIE [7]/.test(ua); 39 40 t.isIE8 = t.isIE && /MSIE [8]/.test(ua); 41 42 t.isIE9 = t.isIE && /MSIE [9]/.test(ua); 43 44 t.isGecko = !t.isWebKit && /Gecko/.test(ua); 45 46 t.isMac = ua.indexOf('Mac') != -1; 47 48 t.isAir = /adobeair/i.test(ua); 49 50 t.isIDevice = /(iPad|iPhone)/.test(ua); 51 52 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; 53 54 // TinyMCE .NET webcontrol might be setting the values for TinyMCE 55 if (win.tinyMCEPreInit) { 56 t.suffix = tinyMCEPreInit.suffix; 57 t.baseURL = tinyMCEPreInit.base; 58 t.query = tinyMCEPreInit.query; 59 return; 60 } 61 62 // Get suffix and base 63 t.suffix = ''; 64 65 // If base element found, add that infront of baseURL 66 nl = d.getElementsByTagName('base'); 67 for (i=0; i<nl.length; i++) { 68 v = nl[i].href; 69 if (v) { 70 // Host only value like http://site.com or http://site.com:8008 71 if (/^https?:\/\/[^\/]+$/.test(v)) 72 v += '/'; 73 74 base = v ? v.match(/.*\//)[0] : ''; // Get only directory 75 } 76 } 77 78 function getBase(n) { 79 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { 80 if (/_(src|dev)\.js/g.test(n.src)) 81 t.suffix = '_src'; 82 83 if ((p = n.src.indexOf('?')) != -1) 84 t.query = n.src.substring(p + 1); 85 86 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/')); 87 88 // If path to script is relative and a base href was found add that one infront 89 // the src property will always be an absolute one on non IE browsers and IE 8 90 // so this logic will basically only be executed on older IE versions 91 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0) 92 t.baseURL = base + t.baseURL; 93 94 return t.baseURL; 95 } 96 97 return null; 98 }; 99 100 // Check document 101 nl = d.getElementsByTagName('script'); 102 for (i=0; i<nl.length; i++) { 103 if (getBase(nl[i])) 104 return; 105 } 106 107 // Check head 108 n = d.getElementsByTagName('head')[0]; 109 if (n) { 110 nl = n.getElementsByTagName('script'); 111 for (i=0; i<nl.length; i++) { 112 if (getBase(nl[i])) 113 return; 114 } 115 } 116 117 return; 118 }, 119 120 is : function(o, t) { 121 if (!t) 122 return o !== undef; 123 124 if (t == 'array' && (o.hasOwnProperty && o instanceof Array)) 125 return true; 126 127 return typeof(o) == t; 128 }, 129 130 makeMap : function(items, delim, map) { 131 var i; 132 133 items = items || []; 134 delim = delim || ','; 135 136 if (typeof(items) == "string") 137 items = items.split(delim); 138 139 map = map || {}; 140 141 i = items.length; 142 while (i--) 143 map[items[i]] = {}; 144 145 return map; 146 }, 147 148 each : function(o, cb, s) { 149 var n, l; 150 151 if (!o) 152 return 0; 153 154 s = s || o; 155 156 if (o.length !== undef) { 157 // Indexed arrays, needed for Safari 158 for (n=0, l = o.length; n < l; n++) { 159 if (cb.call(s, o[n], n, o) === false) 160 return 0; 161 } 162 } else { 163 // Hashtables 164 for (n in o) { 165 if (o.hasOwnProperty(n)) { 166 if (cb.call(s, o[n], n, o) === false) 167 return 0; 168 } 169 } 170 } 171 172 return 1; 173 }, 174 175 176 map : function(a, f) { 177 var o = []; 178 179 tinymce.each(a, function(v) { 180 o.push(f(v)); 181 }); 182 183 return o; 184 }, 185 186 grep : function(a, f) { 187 var o = []; 188 189 tinymce.each(a, function(v) { 190 if (!f || f(v)) 191 o.push(v); 192 }); 193 194 return o; 195 }, 196 197 inArray : function(a, v) { 198 var i, l; 199 200 if (a) { 201 for (i = 0, l = a.length; i < l; i++) { 202 if (a[i] === v) 203 return i; 204 } 205 } 206 207 return -1; 208 }, 209 210 extend : function(obj, ext) { 211 var i, l, name, args = arguments, value; 212 213 for (i = 1, l = args.length; i < l; i++) { 214 ext = args[i]; 215 for (name in ext) { 216 if (ext.hasOwnProperty(name)) { 217 value = ext[name]; 218 219 if (value !== undef) { 220 obj[name] = value; 221 } 222 } 223 } 224 } 225 226 return obj; 227 }, 228 229 230 trim : function(s) { 231 return (s ? '' + s : '').replace(whiteSpaceRe, ''); 232 }, 233 234 create : function(s, p, root) { 235 var t = this, sp, ns, cn, scn, c, de = 0; 236 237 // Parse : <prefix> <class>:<super class> 238 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 239 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 240 241 // Create namespace for new class 242 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); 243 244 // Class already exists 245 if (ns[cn]) 246 return; 247 248 // Make pure static class 249 if (s[2] == 'static') { 250 ns[cn] = p; 251 252 if (this.onCreate) 253 this.onCreate(s[2], s[3], ns[cn]); 254 255 return; 256 } 257 258 // Create default constructor 259 if (!p[cn]) { 260 p[cn] = function() {}; 261 de = 1; 262 } 263 264 // Add constructor and methods 265 ns[cn] = p[cn]; 266 t.extend(ns[cn].prototype, p); 267 268 // Extend 269 if (s[5]) { 270 sp = t.resolve(s[5]).prototype; 271 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 272 273 // Extend constructor 274 c = ns[cn]; 275 if (de) { 276 // Add passthrough constructor 277 ns[cn] = function() { 278 return sp[scn].apply(this, arguments); 279 }; 280 } else { 281 // Add inherit constructor 282 ns[cn] = function() { 283 this.parent = sp[scn]; 284 return c.apply(this, arguments); 285 }; 286 } 287 ns[cn].prototype[cn] = ns[cn]; 288 289 // Add super methods 290 t.each(sp, function(f, n) { 291 ns[cn].prototype[n] = sp[n]; 292 }); 293 294 // Add overridden methods 295 t.each(p, function(f, n) { 296 // Extend methods if needed 297 if (sp[n]) { 298 ns[cn].prototype[n] = function() { 299 this.parent = sp[n]; 300 return f.apply(this, arguments); 301 }; 302 } else { 303 if (n != cn) 304 ns[cn].prototype[n] = f; 305 } 306 }); 307 } 308 309 // Add static methods 310 t.each(p['static'], function(f, n) { 311 ns[cn][n] = f; 312 }); 313 314 if (this.onCreate) 315 this.onCreate(s[2], s[3], ns[cn].prototype); 316 }, 317 318 walk : function(o, f, n, s) { 319 s = s || this; 320 321 if (o) { 322 if (n) 323 o = o[n]; 324 325 tinymce.each(o, function(o, i) { 326 if (f.call(s, o, i, n) === false) 327 return false; 328 329 tinymce.walk(o, f, n, s); 330 }); 331 } 332 }, 333 334 createNS : function(n, o) { 335 var i, v; 336 337 o = o || win; 338 339 n = n.split('.'); 340 for (i=0; i<n.length; i++) { 341 v = n[i]; 342 343 if (!o[v]) 344 o[v] = {}; 345 346 o = o[v]; 347 } 348 349 return o; 350 }, 351 352 resolve : function(n, o) { 353 var i, l; 354 355 o = o || win; 356 357 n = n.split('.'); 358 for (i = 0, l = n.length; i < l; i++) { 359 o = o[n[i]]; 360 361 if (!o) 362 break; 363 } 364 365 return o; 366 }, 367 368 addUnload : function(f, s) { 369 var t = this, unload; 370 371 unload = function() { 372 var li = t.unloads, o, n; 373 374 if (li) { 375 // Call unload handlers 376 for (n in li) { 377 o = li[n]; 378 379 if (o && o.func) 380 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy 381 } 382 383 // Detach unload function 384 if (win.detachEvent) { 385 win.detachEvent('onbeforeunload', fakeUnload); 386 win.detachEvent('onunload', unload); 387 } else if (win.removeEventListener) 388 win.removeEventListener('unload', unload, false); 389 390 // Destroy references 391 t.unloads = o = li = w = unload = 0; 392 393 // Run garbarge collector on IE 394 if (win.CollectGarbage) 395 CollectGarbage(); 396 } 397 }; 398 399 function fakeUnload() { 400 var d = document; 401 402 function stop() { 403 // Prevent memory leak 404 d.detachEvent('onstop', stop); 405 406 // Call unload handler 407 if (unload) 408 unload(); 409 410 d = 0; 411 }; 412 413 // Is there things still loading, then do some magic 414 if (d.readyState == 'interactive') { 415 // Fire unload when the currently loading page is stopped 416 if (d) 417 d.attachEvent('onstop', stop); 418 419 // Remove onstop listener after a while to prevent the unload function 420 // to execute if the user presses cancel in an onbeforeunload 421 // confirm dialog and then presses the browser stop button 422 win.setTimeout(function() { 423 if (d) 424 d.detachEvent('onstop', stop); 425 }, 0); 426 } 427 }; 428 429 f = {func : f, scope : s || this}; 430 431 if (!t.unloads) { 432 // Attach unload handler 433 if (win.attachEvent) { 434 win.attachEvent('onunload', unload); 435 win.attachEvent('onbeforeunload', fakeUnload); 436 } else if (win.addEventListener) 437 win.addEventListener('unload', unload, false); 438 439 // Setup initial unload handler array 440 t.unloads = [f]; 441 } else 442 t.unloads.push(f); 443 444 return f; 445 }, 446 447 removeUnload : function(f) { 448 var u = this.unloads, r = null; 449 450 tinymce.each(u, function(o, i) { 451 if (o && o.func == f) { 452 u.splice(i, 1); 453 r = f; 454 return false; 455 } 456 }); 457 458 return r; 459 }, 460 461 explode : function(s, d) { 462 if (!s || tinymce.is(s, 'array')) { 463 return s; 464 } 465 466 return tinymce.map(s.split(d || ','), tinymce.trim); 467 }, 468 469 _addVer : function(u) { 470 var v; 471 472 if (!this.query) 473 return u; 474 475 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query; 476 477 if (u.indexOf('#') == -1) 478 return u + v; 479 480 return u.replace('#', v + '#'); 481 }, 482 483 // Fix function for IE 9 where regexps isn't working correctly 484 // Todo: remove me once MS fixes the bug 485 _replace : function(find, replace, str) { 486 // On IE9 we have to fake $x replacement 487 if (isRegExpBroken) { 488 return str.replace(find, function() { 489 var val = replace, args = arguments, i; 490 491 for (i = 0; i < args.length - 2; i++) { 492 if (args[i] === undef) { 493 val = val.replace(new RegExp('\\$' + i, 'g'), ''); 494 } else { 495 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); 496 } 497 } 498 499 return val; 500 }); 501 } 502 503 return str.replace(find, replace); 504 } 505 506 }; 507 508 // Initialize the API 509 tinymce._init(); 510 511 // Expose tinymce namespace to the global namespace (window) 512 win.tinymce = win.tinyMCE = tinymce; 513 514 // Describe the different namespaces 515 516 })(window); 517 518 519 520 tinymce.create('tinymce.util.Dispatcher', { 521 scope : null, 522 listeners : null, 523 inDispatch: false, 524 525 Dispatcher : function(scope) { 526 this.scope = scope || this; 527 this.listeners = []; 528 }, 529 530 add : function(callback, scope) { 531 this.listeners.push({cb : callback, scope : scope || this.scope}); 532 533 return callback; 534 }, 535 536 addToTop : function(callback, scope) { 537 var self = this, listener = {cb : callback, scope : scope || self.scope}; 538 539 // Create new listeners if addToTop is executed in a dispatch loop 540 if (self.inDispatch) { 541 self.listeners = [listener].concat(self.listeners); 542 } else { 543 self.listeners.unshift(listener); 544 } 545 546 return callback; 547 }, 548 549 remove : function(callback) { 550 var listeners = this.listeners, output = null; 551 552 tinymce.each(listeners, function(listener, i) { 553 if (callback == listener.cb) { 554 output = listener; 555 listeners.splice(i, 1); 556 return false; 557 } 558 }); 559 560 return output; 561 }, 562 563 dispatch : function() { 564 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener; 565 566 self.inDispatch = true; 567 568 // Needs to be a real loop since the listener count might change while looping 569 // And this is also more efficient 570 for (i = 0; i < listeners.length; i++) { 571 listener = listeners[i]; 572 returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]); 573 574 if (returnValue === false) 575 break; 576 } 577 578 self.inDispatch = false; 579 580 return returnValue; 581 } 582 583 }); 584 585 (function() { 586 var each = tinymce.each; 587 588 tinymce.create('tinymce.util.URI', { 589 URI : function(u, s) { 590 var t = this, o, a, b, base_url; 591 592 // Trim whitespace 593 u = tinymce.trim(u); 594 595 // Default settings 596 s = t.settings = s || {}; 597 598 // Strange app protocol that isn't http/https or local anchor 599 // For example: mailto,skype,tel etc. 600 if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { 601 t.source = u; 602 return; 603 } 604 605 // Absolute path with no host, fake host and protocol 606 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0) 607 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; 608 609 // Relative path http:// or protocol relative //path 610 if (!/^[\w\-]*:?\/\//.test(u)) { 611 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; 612 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); 613 } 614 615 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 616 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 617 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u); 618 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) { 619 var s = u[i]; 620 621 // Zope 3 workaround, they use @@something 622 if (s) 623 s = s.replace(/\(mce_at\)/g, '@@'); 624 625 t[v] = s; 626 }); 627 628 b = s.base_uri; 629 if (b) { 630 if (!t.protocol) 631 t.protocol = b.protocol; 632 633 if (!t.userInfo) 634 t.userInfo = b.userInfo; 635 636 if (!t.port && t.host === 'mce_host') 637 t.port = b.port; 638 639 if (!t.host || t.host === 'mce_host') 640 t.host = b.host; 641 642 t.source = ''; 643 } 644 645 //t.path = t.path || '/'; 646 }, 647 648 setPath : function(p) { 649 var t = this; 650 651 p = /^(.*?)\/?(\w+)?$/.exec(p); 652 653 // Update path parts 654 t.path = p[0]; 655 t.directory = p[1]; 656 t.file = p[2]; 657 658 // Rebuild source 659 t.source = ''; 660 t.getURI(); 661 }, 662 663 toRelative : function(u) { 664 var t = this, o; 665 666 if (u === "./") 667 return u; 668 669 u = new tinymce.util.URI(u, {base_uri : t}); 670 671 // Not on same domain/port or protocol 672 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol) 673 return u.getURI(); 674 675 var tu = t.getURI(), uu = u.getURI(); 676 677 // Allow usage of the base_uri when relative_urls = true 678 if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) 679 return tu; 680 681 o = t.toRelPath(t.path, u.path); 682 683 // Add query 684 if (u.query) 685 o += '?' + u.query; 686 687 // Add anchor 688 if (u.anchor) 689 o += '#' + u.anchor; 690 691 return o; 692 }, 693 694 toAbsolute : function(u, nh) { 695 u = new tinymce.util.URI(u, {base_uri : this}); 696 697 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0); 698 }, 699 700 toRelPath : function(base, path) { 701 var items, bp = 0, out = '', i, l; 702 703 // Split the paths 704 base = base.substring(0, base.lastIndexOf('/')); 705 base = base.split('/'); 706 items = path.split('/'); 707 708 if (base.length >= items.length) { 709 for (i = 0, l = base.length; i < l; i++) { 710 if (i >= items.length || base[i] != items[i]) { 711 bp = i + 1; 712 break; 713 } 714 } 715 } 716 717 if (base.length < items.length) { 718 for (i = 0, l = items.length; i < l; i++) { 719 if (i >= base.length || base[i] != items[i]) { 720 bp = i + 1; 721 break; 722 } 723 } 724 } 725 726 if (bp === 1) 727 return path; 728 729 for (i = 0, l = base.length - (bp - 1); i < l; i++) 730 out += "../"; 731 732 for (i = bp - 1, l = items.length; i < l; i++) { 733 if (i != bp - 1) 734 out += "/" + items[i]; 735 else 736 out += items[i]; 737 } 738 739 return out; 740 }, 741 742 toAbsPath : function(base, path) { 743 var i, nb = 0, o = [], tr, outPath; 744 745 // Split paths 746 tr = /\/$/.test(path) ? '/' : ''; 747 base = base.split('/'); 748 path = path.split('/'); 749 750 // Remove empty chunks 751 each(base, function(k) { 752 if (k) 753 o.push(k); 754 }); 755 756 base = o; 757 758 // Merge relURLParts chunks 759 for (i = path.length - 1, o = []; i >= 0; i--) { 760 // Ignore empty or . 761 if (path[i].length === 0 || path[i] === ".") 762 continue; 763 764 // Is parent 765 if (path[i] === '..') { 766 nb++; 767 continue; 768 } 769 770 // Move up 771 if (nb > 0) { 772 nb--; 773 continue; 774 } 775 776 o.push(path[i]); 777 } 778 779 i = base.length - nb; 780 781 // If /a/b/c or / 782 if (i <= 0) 783 outPath = o.reverse().join('/'); 784 else 785 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 786 787 // Add front / if it's needed 788 if (outPath.indexOf('/') !== 0) 789 outPath = '/' + outPath; 790 791 // Add traling / if it's needed 792 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) 793 outPath += tr; 794 795 return outPath; 796 }, 797 798 getURI : function(nh) { 799 var s, t = this; 800 801 // Rebuild source 802 if (!t.source || nh) { 803 s = ''; 804 805 if (!nh) { 806 if (t.protocol) 807 s += t.protocol + '://'; 808 809 if (t.userInfo) 810 s += t.userInfo + '@'; 811 812 if (t.host) 813 s += t.host; 814 815 if (t.port) 816 s += ':' + t.port; 817 } 818 819 if (t.path) 820 s += t.path; 821 822 if (t.query) 823 s += '?' + t.query; 824 825 if (t.anchor) 826 s += '#' + t.anchor; 827 828 t.source = s; 829 } 830 831 return t.source; 832 } 833 }); 834 })(); 835 836 (function() { 837 var each = tinymce.each; 838 839 tinymce.create('static tinymce.util.Cookie', { 840 getHash : function(n) { 841 var v = this.get(n), h; 842 843 if (v) { 844 each(v.split('&'), function(v) { 845 v = v.split('='); 846 h = h || {}; 847 h[unescape(v[0])] = unescape(v[1]); 848 }); 849 } 850 851 return h; 852 }, 853 854 setHash : function(n, v, e, p, d, s) { 855 var o = ''; 856 857 each(v, function(v, k) { 858 o += (!o ? '' : '&') + escape(k) + '=' + escape(v); 859 }); 860 861 this.set(n, o, e, p, d, s); 862 }, 863 864 get : function(n) { 865 var c = document.cookie, e, p = n + "=", b; 866 867 // Strict mode 868 if (!c) 869 return; 870 871 b = c.indexOf("; " + p); 872 873 if (b == -1) { 874 b = c.indexOf(p); 875 876 if (b !== 0) 877 return null; 878 } else 879 b += 2; 880 881 e = c.indexOf(";", b); 882 883 if (e == -1) 884 e = c.length; 885 886 return unescape(c.substring(b + p.length, e)); 887 }, 888 889 set : function(n, v, e, p, d, s) { 890 document.cookie = n + "=" + escape(v) + 891 ((e) ? "; expires=" + e.toGMTString() : "") + 892 ((p) ? "; path=" + escape(p) : "") + 893 ((d) ? "; domain=" + d : "") + 894 ((s) ? "; secure" : ""); 895 }, 896 897 remove : function(name, path, domain) { 898 var date = new Date(); 899 900 date.setTime(date.getTime() - 1000); 901 902 this.set(name, '', date, path, domain); 903 } 904 }); 905 })(); 906 907 (function() { 908 function serialize(o, quote) { 909 var i, v, t, name; 910 911 quote = quote || '"'; 912 913 if (o == null) 914 return 'null'; 915 916 t = typeof o; 917 918 if (t == 'string') { 919 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 920 921 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 922 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 923 if (quote === '"' && a === "'") 924 return a; 925 926 i = v.indexOf(b); 927 928 if (i + 1) 929 return '\\' + v.charAt(i + 1); 930 931 a = b.charCodeAt().toString(16); 932 933 return '\\u' + '0000'.substring(a.length) + a; 934 }) + quote; 935 } 936 937 if (t == 'object') { 938 if (o.hasOwnProperty && o instanceof Array) { 939 for (i=0, v = '['; i<o.length; i++) 940 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 941 942 return v + ']'; 943 } 944 945 v = '{'; 946 947 for (name in o) { 948 if (o.hasOwnProperty(name)) { 949 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : ''; 950 } 951 } 952 953 return v + '}'; 954 } 955 956 return '' + o; 957 }; 958 959 tinymce.util.JSON = { 960 serialize: serialize, 961 962 parse: function(s) { 963 try { 964 return eval('(' + s + ')'); 965 } catch (ex) { 966 // Ignore 967 } 968 } 969 970 }; 971 })(); 972 973 tinymce.create('static tinymce.util.XHR', { 974 send : function(o) { 975 var x, t, w = window, c = 0; 976 977 function ready() { 978 if (!o.async || x.readyState == 4 || c++ > 10000) { 979 if (o.success && c < 10000 && x.status == 200) 980 o.success.call(o.success_scope, '' + x.responseText, x, o); 981 else if (o.error) 982 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o); 983 984 x = null; 985 } else 986 w.setTimeout(ready, 10); 987 }; 988 989 // Default settings 990 o.scope = o.scope || this; 991 o.success_scope = o.success_scope || o.scope; 992 o.error_scope = o.error_scope || o.scope; 993 o.async = o.async === false ? false : true; 994 o.data = o.data || ''; 995 996 function get(s) { 997 x = 0; 998 999 try { 1000 x = new ActiveXObject(s); 1001 } catch (ex) { 1002 } 1003 1004 return x; 1005 }; 1006 1007 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP'); 1008 1009 if (x) { 1010 if (x.overrideMimeType) 1011 x.overrideMimeType(o.content_type); 1012 1013 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async); 1014 1015 if (o.content_type) 1016 x.setRequestHeader('Content-Type', o.content_type); 1017 1018 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1019 1020 x.send(o.data); 1021 1022 // Syncronous request 1023 if (!o.async) 1024 return ready(); 1025 1026 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 1027 t = w.setTimeout(ready, 10); 1028 } 1029 } 1030 }); 1031 1032 (function() { 1033 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR; 1034 1035 tinymce.create('tinymce.util.JSONRequest', { 1036 JSONRequest : function(s) { 1037 this.settings = extend({ 1038 }, s); 1039 this.count = 0; 1040 }, 1041 1042 send : function(o) { 1043 var ecb = o.error, scb = o.success; 1044 1045 o = extend(this.settings, o); 1046 1047 o.success = function(c, x) { 1048 c = JSON.parse(c); 1049 1050 if (typeof(c) == 'undefined') { 1051 c = { 1052 error : 'JSON Parse error.' 1053 }; 1054 } 1055 1056 if (c.error) 1057 ecb.call(o.error_scope || o.scope, c.error, x); 1058 else 1059 scb.call(o.success_scope || o.scope, c.result); 1060 }; 1061 1062 o.error = function(ty, x) { 1063 if (ecb) 1064 ecb.call(o.error_scope || o.scope, ty, x); 1065 }; 1066 1067 o.data = JSON.serialize({ 1068 id : o.id || 'c' + (this.count++), 1069 method : o.method, 1070 params : o.params 1071 }); 1072 1073 // JSON content type for Ruby on rails. Bug: #1883287 1074 o.content_type = 'application/json'; 1075 1076 XHR.send(o); 1077 }, 1078 1079 'static' : { 1080 sendRPC : function(o) { 1081 return new tinymce.util.JSONRequest().send(o); 1082 } 1083 } 1084 }); 1085 }()); 1086 (function(tinymce){ 1087 tinymce.VK = { 1088 BACKSPACE: 8, 1089 DELETE: 46, 1090 DOWN: 40, 1091 ENTER: 13, 1092 LEFT: 37, 1093 RIGHT: 39, 1094 SPACEBAR: 32, 1095 TAB: 9, 1096 UP: 38, 1097 1098 modifierPressed: function (e) { 1099 return e.shiftKey || e.ctrlKey || e.altKey; 1100 }, 1101 1102 metaKeyPressed: function(e) { 1103 return tinymce.isMac ? e.metaKey : e.ctrlKey; 1104 } 1105 }; 1106 })(tinymce); 1107 1108 tinymce.util.Quirks = function(editor) { 1109 var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings; 1110 1111 function setEditorCommandState(cmd, state) { 1112 try { 1113 editor.getDoc().execCommand(cmd, false, state); 1114 } catch (ex) { 1115 // Ignore 1116 } 1117 } 1118 1119 function getDocumentMode() { 1120 var documentMode = editor.getDoc().documentMode; 1121 1122 return documentMode ? documentMode : 6; 1123 }; 1124 1125 function cleanupStylesWhenDeleting() { 1126 function removeMergedFormatSpans(isDelete) { 1127 var rng, blockElm, node, clonedSpan; 1128 1129 rng = selection.getRng(); 1130 1131 // Find root block 1132 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1133 1134 // On delete clone the root span of the next block element 1135 if (isDelete) 1136 blockElm = dom.getNext(blockElm, dom.isBlock); 1137 1138 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace 1139 if (blockElm) { 1140 node = blockElm.firstChild; 1141 1142 // Ignore empty text nodes 1143 while (node && node.nodeType == 3 && node.nodeValue.length === 0) 1144 node = node.nextSibling; 1145 1146 if (node && node.nodeName === 'SPAN') { 1147 clonedSpan = node.cloneNode(false); 1148 } 1149 } 1150 1151 // Do the backspace/delete action 1152 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1153 1154 // Find all odd apple-style-spans 1155 blockElm = dom.getParent(rng.startContainer, dom.isBlock); 1156 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { 1157 var bm = selection.getBookmark(); 1158 1159 if (clonedSpan) { 1160 dom.replace(clonedSpan.cloneNode(false), span, true); 1161 } else { 1162 dom.remove(span, true); 1163 } 1164 1165 // Restore the selection 1166 selection.moveToBookmark(bm); 1167 }); 1168 }; 1169 1170 editor.onKeyDown.add(function(editor, e) { 1171 var isDelete; 1172 1173 isDelete = e.keyCode == DELETE; 1174 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1175 e.preventDefault(); 1176 removeMergedFormatSpans(isDelete); 1177 } 1178 }); 1179 1180 editor.addCommand('Delete', function() {removeMergedFormatSpans();}); 1181 }; 1182 1183 function emptyEditorWhenDeleting() { 1184 function serializeRng(rng) { 1185 var body = dom.create("body"); 1186 var contents = rng.cloneContents(); 1187 body.appendChild(contents); 1188 return selection.serializer.serialize(body, {format: 'html'}); 1189 } 1190 1191 function allContentsSelected(rng) { 1192 var selection = serializeRng(rng); 1193 1194 var allRng = dom.createRng(); 1195 allRng.selectNode(editor.getBody()); 1196 1197 var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection); 1198 return selection === allSelection; 1199 } 1200 1201 editor.onKeyDown.add(function(editor, e) { 1202 var keyCode = e.keyCode, isCollapsed; 1203 1204 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 1205 if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) { 1206 isCollapsed = editor.selection.isCollapsed(); 1207 1208 // Selection is collapsed but the editor isn't empty 1209 if (isCollapsed && !dom.isEmpty(editor.getBody())) { 1210 return; 1211 } 1212 1213 // IE deletes all contents correctly when everything is selected 1214 if (tinymce.isIE && !isCollapsed) { 1215 return; 1216 } 1217 1218 // Selection isn't collapsed but not all the contents is selected 1219 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 1220 return; 1221 } 1222 1223 // Manually empty the editor 1224 editor.setContent(''); 1225 editor.selection.setCursorLocation(editor.getBody(), 0); 1226 editor.nodeChanged(); 1227 } 1228 }); 1229 }; 1230 1231 function selectAll() { 1232 editor.onKeyDown.add(function(editor, e) { 1233 if (e.keyCode == 65 && VK.metaKeyPressed(e)) { 1234 e.preventDefault(); 1235 editor.execCommand('SelectAll'); 1236 } 1237 }); 1238 }; 1239 1240 function inputMethodFocus() { 1241 if (!editor.settings.content_editable) { 1242 // Case 1 IME doesn't initialize if you focus the document 1243 dom.bind(editor.getDoc(), 'focusin', function(e) { 1244 selection.setRng(selection.getRng()); 1245 }); 1246 1247 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 1248 dom.bind(editor.getDoc(), 'mousedown', function(e) { 1249 if (e.target == editor.getDoc().documentElement) { 1250 editor.getWin().focus(); 1251 selection.setRng(selection.getRng()); 1252 } 1253 }); 1254 } 1255 }; 1256 1257 function removeHrOnBackspace() { 1258 editor.onKeyDown.add(function(editor, e) { 1259 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1260 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1261 var node = selection.getNode(); 1262 var previousSibling = node.previousSibling; 1263 1264 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 1265 dom.remove(previousSibling); 1266 tinymce.dom.Event.cancel(e); 1267 } 1268 } 1269 } 1270 }) 1271 } 1272 1273 function focusBody() { 1274 // Fix for a focus bug in FF 3.x where the body element 1275 // wouldn't get proper focus if the user clicked on the HTML element 1276 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 1277 editor.onMouseDown.add(function(editor, e) { 1278 if (e.target.nodeName === "HTML") { 1279 var body = editor.getBody(); 1280 1281 // Blur the body it's focused but not correctly focused 1282 body.blur(); 1283 1284 // Refocus the body after a little while 1285 setTimeout(function() { 1286 body.focus(); 1287 }, 0); 1288 } 1289 }); 1290 } 1291 }; 1292 1293 function selectControlElements() { 1294 editor.onClick.add(function(editor, e) { 1295 e = e.target; 1296 1297 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 1298 // WebKit can't even do simple things like selecting an image 1299 // Needs tobe the setBaseAndExtend or it will fail to select floated images 1300 if (/^(IMG|HR)$/.test(e.nodeName)) { 1301 selection.getSel().setBaseAndExtent(e, 0, e, 1); 1302 } 1303 1304 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) { 1305 selection.select(e); 1306 } 1307 1308 editor.nodeChanged(); 1309 }); 1310 }; 1311 1312 function removeStylesWhenDeletingAccrossBlockElements() { 1313 function getAttributeApplyFunction() { 1314 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 1315 1316 return function() { 1317 var target = selection.getStart(); 1318 1319 if (target !== editor.getBody()) { 1320 dom.setAttrib(target, "style", null); 1321 1322 tinymce.each(template, function(attr) { 1323 target.setAttributeNode(attr.cloneNode(true)); 1324 }); 1325 } 1326 }; 1327 } 1328 1329 function isSelectionAcrossElements() { 1330 return !selection.isCollapsed() && selection.getStart() != selection.getEnd(); 1331 } 1332 1333 function blockEvent(editor, e) { 1334 e.preventDefault(); 1335 return false; 1336 } 1337 1338 editor.onKeyPress.add(function(editor, e) { 1339 var applyAttributes; 1340 1341 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 1342 applyAttributes = getAttributeApplyFunction(); 1343 editor.getDoc().execCommand('delete', false, null); 1344 applyAttributes(); 1345 e.preventDefault(); 1346 return false; 1347 } 1348 }); 1349 1350 dom.bind(editor.getDoc(), 'cut', function(e) { 1351 var applyAttributes; 1352 1353 if (isSelectionAcrossElements()) { 1354 applyAttributes = getAttributeApplyFunction(); 1355 editor.onKeyUp.addToTop(blockEvent); 1356 1357 setTimeout(function() { 1358 applyAttributes(); 1359 editor.onKeyUp.remove(blockEvent); 1360 }, 0); 1361 } 1362 }); 1363 } 1364 1365 function selectionChangeNodeChanged() { 1366 var lastRng, selectionTimer; 1367 1368 dom.bind(editor.getDoc(), 'selectionchange', function() { 1369 if (selectionTimer) { 1370 clearTimeout(selectionTimer); 1371 selectionTimer = 0; 1372 } 1373 1374 selectionTimer = window.setTimeout(function() { 1375 var rng = selection.getRng(); 1376 1377 // Compare the ranges to see if it was a real change or not 1378 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) { 1379 editor.nodeChanged(); 1380 lastRng = rng; 1381 } 1382 }, 50); 1383 }); 1384 } 1385 1386 function ensureBodyHasRoleApplication() { 1387 document.body.setAttribute("role", "application"); 1388 } 1389 1390 function disableBackspaceIntoATable() { 1391 editor.onKeyDown.add(function(editor, e) { 1392 if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) { 1393 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 1394 var previousSibling = selection.getNode().previousSibling; 1395 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 1396 return tinymce.dom.Event.cancel(e); 1397 } 1398 } 1399 } 1400 }) 1401 } 1402 1403 function addNewLinesBeforeBrInPre() { 1404 // IE8+ rendering mode does the right thing with BR in PRE 1405 if (getDocumentMode() > 7) { 1406 return; 1407 } 1408 1409 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 1410 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 1411 setEditorCommandState('RespectVisibilityInDesign', true); 1412 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 1413 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 1414 1415 // Adds a \n before all BR elements in PRE to get them visual 1416 editor.parser.addNodeFilter('pre', function(nodes, name) { 1417 var i = nodes.length, brNodes, j, brElm, sibling; 1418 1419 while (i--) { 1420 brNodes = nodes[i].getAll('br'); 1421 j = brNodes.length; 1422 while (j--) { 1423 brElm = brNodes[j]; 1424 1425 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 1426 sibling = brElm.prev; 1427 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 1428 sibling.value += '\n'; 1429 } else { 1430 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n'; 1431 } 1432 } 1433 } 1434 }); 1435 1436 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 1437 editor.serializer.addNodeFilter('pre', function(nodes, name) { 1438 var i = nodes.length, brNodes, j, brElm, sibling; 1439 1440 while (i--) { 1441 brNodes = nodes[i].getAll('br'); 1442 j = brNodes.length; 1443 while (j--) { 1444 brElm = brNodes[j]; 1445 sibling = brElm.prev; 1446 if (sibling && sibling.type == 3) { 1447 sibling.value = sibling.value.replace(/\r?\n$/, ''); 1448 } 1449 } 1450 } 1451 }); 1452 } 1453 1454 function removePreSerializedStylesWhenSelectingControls() { 1455 dom.bind(editor.getBody(), 'mouseup', function(e) { 1456 var value, node = selection.getNode(); 1457 1458 // Moved styles to attributes on IMG eements 1459 if (node.nodeName == 'IMG') { 1460 // Convert style width to width attribute 1461 if (value = dom.getStyle(node, 'width')) { 1462 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 1463 dom.setStyle(node, 'width', ''); 1464 } 1465 1466 // Convert style height to height attribute 1467 if (value = dom.getStyle(node, 'height')) { 1468 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 1469 dom.setStyle(node, 'height', ''); 1470 } 1471 } 1472 }); 1473 } 1474 1475 function keepInlineElementOnDeleteBackspace() { 1476 editor.onKeyDown.add(function(editor, e) { 1477 var isDelete, rng, container, offset, brElm, sibling, collapsed; 1478 1479 isDelete = e.keyCode == DELETE; 1480 if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) { 1481 rng = selection.getRng(); 1482 container = rng.startContainer; 1483 offset = rng.startOffset; 1484 collapsed = rng.collapsed; 1485 1486 // Override delete if the start container is a text node and is at the beginning of text or 1487 // just before/after the last character to be deleted in collapsed mode 1488 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) { 1489 nonEmptyElements = editor.schema.getNonEmptyElements(); 1490 1491 // Prevent default logic since it's broken 1492 e.preventDefault(); 1493 1494 // Insert a BR before the text node this will prevent the containing element from being deleted/converted 1495 brElm = dom.create('br', {id: '__tmp'}); 1496 container.parentNode.insertBefore(brElm, container); 1497 1498 // Do the browser delete 1499 editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); 1500 1501 // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p> 1502 container = selection.getRng().startContainer; 1503 sibling = container.previousSibling; 1504 if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) { 1505 dom.remove(sibling); 1506 } 1507 1508 // Remove the temp element we inserted 1509 dom.remove('__tmp'); 1510 } 1511 } 1512 }); 1513 } 1514 1515 function removeBlockQuoteOnBackSpace() { 1516 // Add block quote deletion handler 1517 editor.onKeyDown.add(function(editor, e) { 1518 var rng, container, offset, root, parent; 1519 1520 if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) { 1521 return; 1522 } 1523 1524 rng = selection.getRng(); 1525 container = rng.startContainer; 1526 offset = rng.startOffset; 1527 root = dom.getRoot(); 1528 parent = container; 1529 1530 if (!rng.collapsed || offset !== 0) { 1531 return; 1532 } 1533 1534 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 1535 parent = parent.parentNode; 1536 } 1537 1538 // Is the cursor at the beginning of a blockquote? 1539 if (parent.tagName === 'BLOCKQUOTE') { 1540 // Remove the blockquote 1541 editor.formatter.toggle('blockquote', null, parent); 1542 1543 // Move the caret to the beginning of container 1544 rng.setStart(container, 0); 1545 rng.setEnd(container, 0); 1546 selection.setRng(rng); 1547 selection.collapse(false); 1548 } 1549 }); 1550 }; 1551 1552 function setGeckoEditingOptions() { 1553 function setOpts() { 1554 editor._refreshContentEditable(); 1555 1556 setEditorCommandState("StyleWithCSS", false); 1557 setEditorCommandState("enableInlineTableEditing", false); 1558 1559 if (!settings.object_resizing) { 1560 setEditorCommandState("enableObjectResizing", false); 1561 } 1562 }; 1563 1564 if (!settings.readonly) { 1565 editor.onBeforeExecCommand.add(setOpts); 1566 editor.onMouseDown.add(setOpts); 1567 } 1568 }; 1569 1570 function addBrAfterLastLinks() { 1571 function fixLinks(editor, o) { 1572 tinymce.each(dom.select('a'), function(node) { 1573 var parentNode = node.parentNode, root = dom.getRoot(); 1574 1575 if (parentNode.lastChild === node) { 1576 while (parentNode && !dom.isBlock(parentNode)) { 1577 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 1578 return; 1579 } 1580 1581 parentNode = parentNode.parentNode; 1582 } 1583 1584 dom.add(parentNode, 'br', {'data-mce-bogus' : 1}); 1585 } 1586 }); 1587 }; 1588 1589 editor.onExecCommand.add(function(editor, cmd) { 1590 if (cmd === 'CreateLink') { 1591 fixLinks(editor); 1592 } 1593 }); 1594 1595 editor.onSetContent.add(selection.onSetContent.add(fixLinks)); 1596 }; 1597 1598 function setDefaultBlockType() { 1599 if (settings.forced_root_block) { 1600 editor.onInit.add(function() { 1601 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 1602 }); 1603 } 1604 } 1605 1606 function removeGhostSelection() { 1607 function repaint(sender, args) { 1608 if (!sender || !args.initial) { 1609 editor.execCommand('mceRepaint'); 1610 } 1611 }; 1612 1613 editor.onUndo.add(repaint); 1614 editor.onRedo.add(repaint); 1615 editor.onSetContent.add(repaint); 1616 }; 1617 1618 function deleteControlItemOnBackSpace() { 1619 editor.onKeyDown.add(function(editor, e) { 1620 var rng; 1621 1622 if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) { 1623 rng = editor.getDoc().selection.createRange(); 1624 if (rng && rng.item) { 1625 e.preventDefault(); 1626 editor.undoManager.beforeChange(); 1627 dom.remove(rng.item(0)); 1628 editor.undoManager.add(); 1629 } 1630 } 1631 }); 1632 }; 1633 1634 function renderEmptyBlocksFix() { 1635 var emptyBlocksCSS; 1636 1637 // IE10+ 1638 if (getDocumentMode() >= 10) { 1639 emptyBlocksCSS = ''; 1640 tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 1641 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 1642 }); 1643 1644 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 1645 } 1646 }; 1647 1648 function fakeImageResize() { 1649 var mouseDownImg, startX, startY, startW, startH; 1650 1651 if (!settings.object_resizing || settings.webkit_fake_resize === false) { 1652 return; 1653 } 1654 1655 editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}'); 1656 1657 function resizeImage(e) { 1658 var deltaX, deltaY, ratio, width, height; 1659 1660 if (mouseDownImg) { 1661 deltaX = e.screenX - startX; 1662 deltaY = e.screenY - startY; 1663 ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH); 1664 1665 // Only update styles if the user draged one pixel or more 1666 if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) { 1667 // Constrain proportions 1668 width = Math.round(startW * ratio); 1669 height = Math.round(startH * ratio); 1670 1671 // Resize by using style or attribute 1672 if (mouseDownImg.style.width) { 1673 dom.setStyle(mouseDownImg, 'width', width); 1674 } else { 1675 dom.setAttrib(mouseDownImg, 'width', width); 1676 } 1677 1678 // Resize by using style or attribute 1679 if (mouseDownImg.style.height) { 1680 dom.setStyle(mouseDownImg, 'height', height); 1681 } else { 1682 dom.setAttrib(mouseDownImg, 'height', height); 1683 } 1684 1685 if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) { 1686 dom.addClass(editor.getBody(), 'mceResizeImages'); 1687 } 1688 } 1689 } 1690 }; 1691 1692 editor.onMouseDown.add(function(editor, e) { 1693 var target = e.target; 1694 1695 if (target.nodeName == "IMG") { 1696 mouseDownImg = target; 1697 startX = e.screenX; 1698 startY = e.screenY; 1699 startW = mouseDownImg.clientWidth; 1700 startH = mouseDownImg.clientHeight; 1701 dom.bind(editor.getDoc(), 'mousemove', resizeImage); 1702 e.preventDefault(); 1703 } 1704 }); 1705 1706 // Unbind events on node change and restore resize cursor 1707 editor.onNodeChange.add(function() { 1708 if (mouseDownImg) { 1709 mouseDownImg = null; 1710 dom.unbind(editor.getDoc(), 'mousemove', resizeImage); 1711 } 1712 1713 if (selection.getNode().nodeName == "IMG") { 1714 dom.addClass(editor.getBody(), 'mceResizeImages'); 1715 } else { 1716 dom.removeClass(editor.getBody(), 'mceResizeImages'); 1717 } 1718 }); 1719 }; 1720 1721 // All browsers 1722 disableBackspaceIntoATable(); 1723 removeBlockQuoteOnBackSpace(); 1724 emptyEditorWhenDeleting(); 1725 1726 // WebKit 1727 if (tinymce.isWebKit) { 1728 keepInlineElementOnDeleteBackspace(); 1729 cleanupStylesWhenDeleting(); 1730 inputMethodFocus(); 1731 selectControlElements(); 1732 setDefaultBlockType(); 1733 1734 // iOS 1735 if (tinymce.isIDevice) { 1736 selectionChangeNodeChanged(); 1737 } else { 1738 fakeImageResize(); 1739 selectAll(); 1740 } 1741 } 1742 1743 // IE 1744 if (tinymce.isIE) { 1745 removeHrOnBackspace(); 1746 ensureBodyHasRoleApplication(); 1747 addNewLinesBeforeBrInPre(); 1748 removePreSerializedStylesWhenSelectingControls(); 1749 deleteControlItemOnBackSpace(); 1750 renderEmptyBlocksFix(); 1751 } 1752 1753 // Gecko 1754 if (tinymce.isGecko) { 1755 removeHrOnBackspace(); 1756 focusBody(); 1757 removeStylesWhenDeletingAccrossBlockElements(); 1758 setGeckoEditingOptions(); 1759 addBrAfterLastLinks(); 1760 removeGhostSelection(); 1761 } 1762 }; 1763 (function(tinymce) { 1764 var namedEntities, baseEntities, reverseEntities, 1765 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1766 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 1767 rawCharsRegExp = /[<>&\"\']/g, 1768 entityRegExp = /&(#x|#)?([\w]+);/g, 1769 asciiMap = { 1770 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", 1771 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", 1772 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", 1773 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", 1774 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" 1775 }; 1776 1777 // Raw entities 1778 baseEntities = { 1779 '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code 1780 "'" : ''', 1781 '<' : '<', 1782 '>' : '>', 1783 '&' : '&' 1784 }; 1785 1786 // Reverse lookup table for raw entities 1787 reverseEntities = { 1788 '<' : '<', 1789 '>' : '>', 1790 '&' : '&', 1791 '"' : '"', 1792 ''' : "'" 1793 }; 1794 1795 // Decodes text by using the browser 1796 function nativeDecode(text) { 1797 var elm; 1798 1799 elm = document.createElement("div"); 1800 elm.innerHTML = text; 1801 1802 return elm.textContent || elm.innerText || text; 1803 }; 1804 1805 // Build a two way lookup table for the entities 1806 function buildEntitiesLookup(items, radix) { 1807 var i, chr, entity, lookup = {}; 1808 1809 if (items) { 1810 items = items.split(','); 1811 radix = radix || 10; 1812 1813 // Build entities lookup table 1814 for (i = 0; i < items.length; i += 2) { 1815 chr = String.fromCharCode(parseInt(items[i], radix)); 1816 1817 // Only add non base entities 1818 if (!baseEntities[chr]) { 1819 entity = '&' + items[i + 1] + ';'; 1820 lookup[chr] = entity; 1821 lookup[entity] = chr; 1822 } 1823 } 1824 1825 return lookup; 1826 } 1827 }; 1828 1829 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 1830 namedEntities = buildEntitiesLookup( 1831 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 1832 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 1833 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 1834 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 1835 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 1836 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 1837 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 1838 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 1839 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 1840 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 1841 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 1842 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 1843 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 1844 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 1845 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 1846 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 1847 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 1848 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 1849 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 1850 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 1851 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 1852 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 1853 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 1854 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 1855 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 1856 1857 tinymce.html = tinymce.html || {}; 1858 1859 tinymce.html.Entities = { 1860 encodeRaw : function(text, attr) { 1861 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1862 return baseEntities[chr] || chr; 1863 }); 1864 }, 1865 1866 encodeAllRaw : function(text) { 1867 return ('' + text).replace(rawCharsRegExp, function(chr) { 1868 return baseEntities[chr] || chr; 1869 }); 1870 }, 1871 1872 encodeNumeric : function(text, attr) { 1873 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1874 // Multi byte sequence convert it to a single entity 1875 if (chr.length > 1) 1876 return '' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 1877 1878 return baseEntities[chr] || '' + chr.charCodeAt(0) + ';'; 1879 }); 1880 }, 1881 1882 encodeNamed : function(text, attr, entities) { 1883 entities = entities || namedEntities; 1884 1885 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1886 return baseEntities[chr] || entities[chr] || chr; 1887 }); 1888 }, 1889 1890 getEncodeFunc : function(name, entities) { 1891 var Entities = tinymce.html.Entities; 1892 1893 entities = buildEntitiesLookup(entities) || namedEntities; 1894 1895 function encodeNamedAndNumeric(text, attr) { 1896 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 1897 return baseEntities[chr] || entities[chr] || '' + chr.charCodeAt(0) + ';' || chr; 1898 }); 1899 }; 1900 1901 function encodeCustomNamed(text, attr) { 1902 return Entities.encodeNamed(text, attr, entities); 1903 }; 1904 1905 // Replace + with , to be compatible with previous TinyMCE versions 1906 name = tinymce.makeMap(name.replace(/\+/g, ',')); 1907 1908 // Named and numeric encoder 1909 if (name.named && name.numeric) 1910 return encodeNamedAndNumeric; 1911 1912 // Named encoder 1913 if (name.named) { 1914 // Custom names 1915 if (entities) 1916 return encodeCustomNamed; 1917 1918 return Entities.encodeNamed; 1919 } 1920 1921 // Numeric 1922 if (name.numeric) 1923 return Entities.encodeNumeric; 1924 1925 // Raw encoder 1926 return Entities.encodeRaw; 1927 }, 1928 1929 decode : function(text) { 1930 return text.replace(entityRegExp, function(all, numeric, value) { 1931 if (numeric) { 1932 value = parseInt(value, numeric.length === 2 ? 16 : 10); 1933 1934 // Support upper UTF 1935 if (value > 0xFFFF) { 1936 value -= 0x10000; 1937 1938 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); 1939 } else 1940 return asciiMap[value] || String.fromCharCode(value); 1941 } 1942 1943 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 1944 }); 1945 } 1946 }; 1947 })(tinymce); 1948 1949 tinymce.html.Styles = function(settings, schema) { 1950 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 1951 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 1952 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 1953 trimRightRegExp = /\s+$/, 1954 urlColorRegExp = /rgb/, 1955 undef, i, encodingLookup = {}, encodingItems; 1956 1957 settings = settings || {}; 1958 1959 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); 1960 for (i = 0; i < encodingItems.length; i++) { 1961 encodingLookup[encodingItems[i]] = '\uFEFF' + i; 1962 encodingLookup['\uFEFF' + i] = encodingItems[i]; 1963 } 1964 1965 function toHex(match, r, g, b) { 1966 function hex(val) { 1967 val = parseInt(val).toString(16); 1968 1969 return val.length > 1 ? val : '0' + val; // 0 -> 00 1970 }; 1971 1972 return '#' + hex(r) + hex(g) + hex(b); 1973 }; 1974 1975 return { 1976 toHex : function(color) { 1977 return color.replace(rgbRegExp, toHex); 1978 }, 1979 1980 parse : function(css) { 1981 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; 1982 1983 function compress(prefix, suffix) { 1984 var top, right, bottom, left; 1985 1986 // Get values and check it it needs compressing 1987 top = styles[prefix + '-top' + suffix]; 1988 if (!top) 1989 return; 1990 1991 right = styles[prefix + '-right' + suffix]; 1992 if (top != right) 1993 return; 1994 1995 bottom = styles[prefix + '-bottom' + suffix]; 1996 if (right != bottom) 1997 return; 1998 1999 left = styles[prefix + '-left' + suffix]; 2000 if (bottom != left) 2001 return; 2002 2003 // Compress 2004 styles[prefix + suffix] = left; 2005 delete styles[prefix + '-top' + suffix]; 2006 delete styles[prefix + '-right' + suffix]; 2007 delete styles[prefix + '-bottom' + suffix]; 2008 delete styles[prefix + '-left' + suffix]; 2009 }; 2010 2011 function canCompress(key) { 2012 var value = styles[key], i; 2013 2014 if (!value || value.indexOf(' ') < 0) 2015 return; 2016 2017 value = value.split(' '); 2018 i = value.length; 2019 while (i--) { 2020 if (value[i] !== value[0]) 2021 return false; 2022 } 2023 2024 styles[key] = value[0]; 2025 2026 return true; 2027 }; 2028 2029 function compress2(target, a, b, c) { 2030 if (!canCompress(a)) 2031 return; 2032 2033 if (!canCompress(b)) 2034 return; 2035 2036 if (!canCompress(c)) 2037 return; 2038 2039 // Compress 2040 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 2041 delete styles[a]; 2042 delete styles[b]; 2043 delete styles[c]; 2044 }; 2045 2046 // Encodes the specified string by replacing all \" \' ; : with _<num> 2047 function encode(str) { 2048 isEncoded = true; 2049 2050 return encodingLookup[str]; 2051 }; 2052 2053 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 2054 // It will also decode the \" \' if keep_slashes is set to fale or omitted 2055 function decode(str, keep_slashes) { 2056 if (isEncoded) { 2057 str = str.replace(/\uFEFF[0-9]/g, function(str) { 2058 return encodingLookup[str]; 2059 }); 2060 } 2061 2062 if (!keep_slashes) 2063 str = str.replace(/\\([\'\";:])/g, "$1"); 2064 2065 return str; 2066 }; 2067 2068 function processUrl(match, url, url2, url3, str, str2) { 2069 str = str || str2; 2070 2071 if (str) { 2072 str = decode(str); 2073 2074 // Force strings into single quote format 2075 return "'" + str.replace(/\'/g, "\\'") + "'"; 2076 } 2077 2078 url = decode(url || url2 || url3); 2079 2080 // Convert the URL to relative/absolute depending on config 2081 if (urlConverter) 2082 url = urlConverter.call(urlConverterScope, url, 'style'); 2083 2084 // Output new URL format 2085 return "url('" + url.replace(/\'/g, "\\'") + "')"; 2086 }; 2087 2088 if (css) { 2089 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 2090 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 2091 return str.replace(/[;:]/g, encode); 2092 }); 2093 2094 // Parse styles 2095 while (matches = styleRegExp.exec(css)) { 2096 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 2097 value = matches[2].replace(trimRightRegExp, ''); 2098 2099 if (name && value.length > 0) { 2100 // Opera will produce 700 instead of bold in their style values 2101 if (name === 'font-weight' && value === '700') 2102 value = 'bold'; 2103 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED 2104 value = value.toLowerCase(); 2105 2106 // Convert RGB colors to HEX 2107 value = value.replace(rgbRegExp, toHex); 2108 2109 // Convert URLs and force them into url('value') format 2110 value = value.replace(urlOrStrRegExp, processUrl); 2111 styles[name] = isEncoded ? decode(value, true) : value; 2112 } 2113 2114 styleRegExp.lastIndex = matches.index + matches[0].length; 2115 } 2116 2117 // Compress the styles to reduce it's size for example IE will expand styles 2118 compress("border", ""); 2119 compress("border", "-width"); 2120 compress("border", "-color"); 2121 compress("border", "-style"); 2122 compress("padding", ""); 2123 compress("margin", ""); 2124 compress2('border', 'border-width', 'border-style', 'border-color'); 2125 2126 // Remove pointless border, IE produces these 2127 if (styles.border === 'medium none') 2128 delete styles.border; 2129 } 2130 2131 return styles; 2132 }, 2133 2134 serialize : function(styles, element_name) { 2135 var css = '', name, value; 2136 2137 function serializeStyles(name) { 2138 var styleList, i, l, value; 2139 2140 styleList = schema.styles[name]; 2141 if (styleList) { 2142 for (i = 0, l = styleList.length; i < l; i++) { 2143 name = styleList[i]; 2144 value = styles[name]; 2145 2146 if (value !== undef && value.length > 0) 2147 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2148 } 2149 } 2150 }; 2151 2152 // Serialize styles according to schema 2153 if (element_name && schema && schema.styles) { 2154 // Serialize global styles and element specific styles 2155 serializeStyles('*'); 2156 serializeStyles(element_name); 2157 } else { 2158 // Output the styles in the order they are inside the object 2159 for (name in styles) { 2160 value = styles[name]; 2161 2162 if (value !== undef && value.length > 0) 2163 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 2164 } 2165 } 2166 2167 return css; 2168 } 2169 }; 2170 }; 2171 2172 (function(tinymce) { 2173 var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each; 2174 2175 function split(str, delim) { 2176 return str.split(delim || ','); 2177 }; 2178 2179 function unpack(lookup, data) { 2180 var key, elements = {}; 2181 2182 function replace(value) { 2183 return value.replace(/[A-Z]+/g, function(key) { 2184 return replace(lookup[key]); 2185 }); 2186 }; 2187 2188 // Unpack lookup 2189 for (key in lookup) { 2190 if (lookup.hasOwnProperty(key)) 2191 lookup[key] = replace(lookup[key]); 2192 } 2193 2194 // Unpack and parse data into object map 2195 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { 2196 attributes = split(attributes, '|'); 2197 2198 elements[name] = { 2199 attributes : makeMap(attributes), 2200 attributesOrder : attributes, 2201 children : makeMap(children, '|', {'#comment' : {}}) 2202 } 2203 }); 2204 2205 return elements; 2206 }; 2207 2208 function getHTML5() { 2209 var html5 = mapCache.html5; 2210 2211 if (!html5) { 2212 html5 = mapCache.html5 = unpack({ 2213 A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2214 B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' + 2215 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr', 2216 C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' + 2217 'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' + 2218 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video' 2219 }, 'html[A|manifest][body|head]' + 2220 'head[A][base|command|link|meta|noscript|script|style|title]' + 2221 'title[A][#]' + 2222 'base[A|href|target][]' + 2223 'link[A|href|rel|media|type|sizes][]' + 2224 'meta[A|http-equiv|name|content|charset][]' + 2225 'style[A|type|media|scoped][#]' + 2226 'script[A|charset|type|src|defer|async][#]' + 2227 'noscript[A][C]' + 2228 'body[A][C]' + 2229 'section[A][C]' + 2230 'nav[A][C]' + 2231 'article[A][C]' + 2232 'aside[A][C]' + 2233 'h1[A][B]' + 2234 'h2[A][B]' + 2235 'h3[A][B]' + 2236 'h4[A][B]' + 2237 'h5[A][B]' + 2238 'h6[A][B]' + 2239 'hgroup[A][h1|h2|h3|h4|h5|h6]' + 2240 'header[A][C]' + 2241 'footer[A][C]' + 2242 'address[A][C]' + 2243 'p[A][B]' + 2244 'br[A][]' + 2245 'pre[A][B]' + 2246 'dialog[A][dd|dt]' + 2247 'blockquote[A|cite][C]' + 2248 'ol[A|start|reversed][li]' + 2249 'ul[A][li]' + 2250 'li[A|value][C]' + 2251 'dl[A][dd|dt]' + 2252 'dt[A][B]' + 2253 'dd[A][C]' + 2254 'a[A|href|target|ping|rel|media|type][B]' + 2255 'em[A][B]' + 2256 'strong[A][B]' + 2257 'small[A][B]' + 2258 'cite[A][B]' + 2259 'q[A|cite][B]' + 2260 'dfn[A][B]' + 2261 'abbr[A][B]' + 2262 'code[A][B]' + 2263 'var[A][B]' + 2264 'samp[A][B]' + 2265 'kbd[A][B]' + 2266 'sub[A][B]' + 2267 'sup[A][B]' + 2268 'i[A][B]' + 2269 'b[A][B]' + 2270 'mark[A][B]' + 2271 'progress[A|value|max][B]' + 2272 'meter[A|value|min|max|low|high|optimum][B]' + 2273 'time[A|datetime][B]' + 2274 'ruby[A][B|rt|rp]' + 2275 'rt[A][B]' + 2276 'rp[A][B]' + 2277 'bdo[A][B]' + 2278 'span[A][B]' + 2279 'ins[A|cite|datetime][B]' + 2280 'del[A|cite|datetime][B]' + 2281 'figure[A][C|legend|figcaption]' + 2282 'figcaption[A][C]' + 2283 'img[A|alt|src|height|width|usemap|ismap][]' + 2284 'iframe[A|name|src|height|width|sandbox|seamless][]' + 2285 'embed[A|src|height|width|type][]' + 2286 'object[A|data|type|height|width|usemap|name|form|classid][param]' + 2287 'param[A|name|value][]' + 2288 'details[A|open][C|legend]' + 2289 'command[A|type|label|icon|disabled|checked|radiogroup][]' + 2290 'menu[A|type|label][C|li]' + 2291 'legend[A][C|B]' + 2292 'div[A][C]' + 2293 'source[A|src|type|media][]' + 2294 'audio[A|src|autobuffer|autoplay|loop|controls][source]' + 2295 'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' + 2296 'hr[A][]' + 2297 'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' + 2298 'fieldset[A|disabled|form|name][C|legend]' + 2299 'label[A|form|for][B]' + 2300 'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' + 2301 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' + 2302 'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' + 2303 'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' + 2304 'datalist[A][B|option]' + 2305 'optgroup[A|disabled|label][option]' + 2306 'option[A|disabled|selected|label|value][]' + 2307 'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' + 2308 'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' + 2309 'output[A|for|form|name][B]' + 2310 'canvas[A|width|height][]' + 2311 'map[A|name][B|C]' + 2312 'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' + 2313 'mathml[A][]' + 2314 'svg[A][]' + 2315 'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' + 2316 'caption[A][C]' + 2317 'colgroup[A|span][col]' + 2318 'col[A|span][]' + 2319 'thead[A][tr]' + 2320 'tfoot[A][tr]' + 2321 'tbody[A][tr]' + 2322 'tr[A][th|td]' + 2323 'th[A|headers|rowspan|colspan|scope][B]' + 2324 'td[A|headers|rowspan|colspan][C]' + 2325 'wbr[A][]' 2326 ); 2327 } 2328 2329 return html5; 2330 }; 2331 2332 function getHTML4() { 2333 var html4 = mapCache.html4; 2334 2335 if (!html4) { 2336 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size 2337 html4 = mapCache.html4 = unpack({ 2338 Z : 'H|K|N|O|P', 2339 Y : 'X|form|R|Q', 2340 ZG : 'E|span|width|align|char|charoff|valign', 2341 X : 'p|T|div|U|W|isindex|fieldset|table', 2342 ZF : 'E|align|char|charoff|valign', 2343 W : 'pre|hr|blockquote|address|center|noframes', 2344 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', 2345 ZD : '[E][S]', 2346 U : 'ul|ol|dl|menu|dir', 2347 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', 2348 T : 'h1|h2|h3|h4|h5|h6', 2349 ZB : 'X|S|Q', 2350 S : 'R|P', 2351 ZA : 'a|G|J|M|O|P', 2352 R : 'a|H|K|N|O', 2353 Q : 'noscript|P', 2354 P : 'ins|del|script', 2355 O : 'input|select|textarea|label|button', 2356 N : 'M|L', 2357 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', 2358 L : 'sub|sup', 2359 K : 'J|I', 2360 J : 'tt|i|b|u|s|strike', 2361 I : 'big|small|font|basefont', 2362 H : 'G|F', 2363 G : 'br|span|bdo', 2364 F : 'object|applet|img|map|iframe', 2365 E : 'A|B|C', 2366 D : 'accesskey|tabindex|onfocus|onblur', 2367 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', 2368 B : 'lang|xml:lang|dir', 2369 A : 'id|class|style|title' 2370 }, 'script[id|charset|type|language|src|defer|xml:space][]' + 2371 'style[B|id|type|media|title|xml:space][]' + 2372 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 2373 'param[id|name|value|valuetype|type][]' + 2374 'p[E|align][#|S]' + 2375 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 2376 'br[A|clear][]' + 2377 'span[E][#|S]' + 2378 'bdo[A|C|B][#|S]' + 2379 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 2380 'h1[E|align][#|S]' + 2381 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 2382 'map[B|C|A|name][X|form|Q|area]' + 2383 'h2[E|align][#|S]' + 2384 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 2385 'h3[E|align][#|S]' + 2386 'tt[E][#|S]' + 2387 'i[E][#|S]' + 2388 'b[E][#|S]' + 2389 'u[E][#|S]' + 2390 's[E][#|S]' + 2391 'strike[E][#|S]' + 2392 'big[E][#|S]' + 2393 'small[E][#|S]' + 2394 'font[A|B|size|color|face][#|S]' + 2395 'basefont[id|size|color|face][]' + 2396 'em[E][#|S]' + 2397 'strong[E][#|S]' + 2398 'dfn[E][#|S]' + 2399 'code[E][#|S]' + 2400 'q[E|cite][#|S]' + 2401 'samp[E][#|S]' + 2402 'kbd[E][#|S]' + 2403 'var[E][#|S]' + 2404 'cite[E][#|S]' + 2405 'abbr[E][#|S]' + 2406 'acronym[E][#|S]' + 2407 'sub[E][#|S]' + 2408 'sup[E][#|S]' + 2409 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 2410 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 2411 'optgroup[E|disabled|label][option]' + 2412 'option[E|selected|disabled|label|value][]' + 2413 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 2414 'label[E|for|accesskey|onfocus|onblur][#|S]' + 2415 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 2416 'h4[E|align][#|S]' + 2417 'ins[E|cite|datetime][#|Y]' + 2418 'h5[E|align][#|S]' + 2419 'del[E|cite|datetime][#|Y]' + 2420 'h6[E|align][#|S]' + 2421 'div[E|align][#|Y]' + 2422 'ul[E|type|compact][li]' + 2423 'li[E|type|value][#|Y]' + 2424 'ol[E|type|compact|start][li]' + 2425 'dl[E|compact][dt|dd]' + 2426 'dt[E][#|S]' + 2427 'dd[E][#|Y]' + 2428 'menu[E|compact][li]' + 2429 'dir[E|compact][li]' + 2430 'pre[E|width|xml:space][#|ZA]' + 2431 'hr[E|align|noshade|size|width][]' + 2432 'blockquote[E|cite][#|Y]' + 2433 'address[E][#|S|p]' + 2434 'center[E][#|Y]' + 2435 'noframes[E][#|Y]' + 2436 'isindex[A|B|prompt][]' + 2437 'fieldset[E][#|legend|Y]' + 2438 'legend[E|accesskey|align][#|S]' + 2439 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 2440 'caption[E|align][#|S]' + 2441 'col[ZG][]' + 2442 'colgroup[ZG][col]' + 2443 'thead[ZF][tr]' + 2444 'tr[ZF|bgcolor][th|td]' + 2445 'th[E|ZE][#|Y]' + 2446 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 2447 'noscript[E][#|Y]' + 2448 'td[E|ZE][#|Y]' + 2449 'tfoot[ZF][tr]' + 2450 'tbody[ZF][tr]' + 2451 'area[E|D|shape|coords|href|nohref|alt|target][]' + 2452 'base[id|href|target][]' + 2453 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' 2454 ); 2455 } 2456 2457 return html4; 2458 }; 2459 2460 tinymce.html.Schema = function(settings) { 2461 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems; 2462 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {}; 2463 2464 // Creates an lookup table map object for the specified option or the default value 2465 function createLookupTable(option, default_value, extend) { 2466 var value = settings[option]; 2467 2468 if (!value) { 2469 // Get cached default map or make it if needed 2470 value = mapCache[option]; 2471 2472 if (!value) { 2473 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 2474 value = tinymce.extend(value, extend); 2475 2476 mapCache[option] = value; 2477 } 2478 } else { 2479 // Create custom map 2480 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' ')); 2481 } 2482 2483 return value; 2484 }; 2485 2486 settings = settings || {}; 2487 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4(); 2488 2489 // Allow all elements and attributes if verify_html is set to false 2490 if (settings.verify_html === false) 2491 settings.valid_elements = '*[*]'; 2492 2493 // Build styles list 2494 if (settings.valid_styles) { 2495 validStyles = {}; 2496 2497 // Convert styles into a rule list 2498 each(settings.valid_styles, function(value, key) { 2499 validStyles[key] = tinymce.explode(value); 2500 }); 2501 } 2502 2503 // Setup map objects 2504 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea'); 2505 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 2506 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr'); 2507 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls'); 2508 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap); 2509 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 2510 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 2511 'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup'); 2512 2513 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 2514 function patternToRegExp(str) { 2515 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 2516 }; 2517 2518 // Parses the specified valid_elements string and adds to the current rules 2519 // This function is a bit hard to read since it's heavily optimized for speed 2520 function addValidElements(valid_elements) { 2521 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 2522 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, 2523 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, 2524 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 2525 hasPatternsRegExp = /[*?+]/; 2526 2527 if (valid_elements) { 2528 // Split valid elements into an array with rules 2529 valid_elements = split(valid_elements); 2530 2531 if (elements['@']) { 2532 globalAttributes = elements['@'].attributes; 2533 globalAttributesOrder = elements['@'].attributesOrder; 2534 } 2535 2536 // Loop all rules 2537 for (ei = 0, el = valid_elements.length; ei < el; ei++) { 2538 // Parse element rule 2539 matches = elementRuleRegExp.exec(valid_elements[ei]); 2540 if (matches) { 2541 // Setup local names for matches 2542 prefix = matches[1]; 2543 elementName = matches[2]; 2544 outputName = matches[3]; 2545 attrData = matches[4]; 2546 2547 // Create new attributes and attributesOrder 2548 attributes = {}; 2549 attributesOrder = []; 2550 2551 // Create the new element 2552 element = { 2553 attributes : attributes, 2554 attributesOrder : attributesOrder 2555 }; 2556 2557 // Padd empty elements prefix 2558 if (prefix === '#') 2559 element.paddEmpty = true; 2560 2561 // Remove empty elements prefix 2562 if (prefix === '-') 2563 element.removeEmpty = true; 2564 2565 // Copy attributes from global rule into current rule 2566 if (globalAttributes) { 2567 for (key in globalAttributes) 2568 attributes[key] = globalAttributes[key]; 2569 2570 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 2571 } 2572 2573 // Attributes defined 2574 if (attrData) { 2575 attrData = split(attrData, '|'); 2576 for (ai = 0, al = attrData.length; ai < al; ai++) { 2577 matches = attrRuleRegExp.exec(attrData[ai]); 2578 if (matches) { 2579 attr = {}; 2580 attrType = matches[1]; 2581 attrName = matches[2].replace(/::/g, ':'); 2582 prefix = matches[3]; 2583 value = matches[4]; 2584 2585 // Required 2586 if (attrType === '!') { 2587 element.attributesRequired = element.attributesRequired || []; 2588 element.attributesRequired.push(attrName); 2589 attr.required = true; 2590 } 2591 2592 // Denied from global 2593 if (attrType === '-') { 2594 delete attributes[attrName]; 2595 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); 2596 continue; 2597 } 2598 2599 // Default value 2600 if (prefix) { 2601 // Default value 2602 if (prefix === '=') { 2603 element.attributesDefault = element.attributesDefault || []; 2604 element.attributesDefault.push({name: attrName, value: value}); 2605 attr.defaultValue = value; 2606 } 2607 2608 // Forced value 2609 if (prefix === ':') { 2610 element.attributesForced = element.attributesForced || []; 2611 element.attributesForced.push({name: attrName, value: value}); 2612 attr.forcedValue = value; 2613 } 2614 2615 // Required values 2616 if (prefix === '<') 2617 attr.validValues = makeMap(value, '?'); 2618 } 2619 2620 // Check for attribute patterns 2621 if (hasPatternsRegExp.test(attrName)) { 2622 element.attributePatterns = element.attributePatterns || []; 2623 attr.pattern = patternToRegExp(attrName); 2624 element.attributePatterns.push(attr); 2625 } else { 2626 // Add attribute to order list if it doesn't already exist 2627 if (!attributes[attrName]) 2628 attributesOrder.push(attrName); 2629 2630 attributes[attrName] = attr; 2631 } 2632 } 2633 } 2634 } 2635 2636 // Global rule, store away these for later usage 2637 if (!globalAttributes && elementName == '@') { 2638 globalAttributes = attributes; 2639 globalAttributesOrder = attributesOrder; 2640 } 2641 2642 // Handle substitute elements such as b/strong 2643 if (outputName) { 2644 element.outputName = elementName; 2645 elements[outputName] = element; 2646 } 2647 2648 // Add pattern or exact element 2649 if (hasPatternsRegExp.test(elementName)) { 2650 element.pattern = patternToRegExp(elementName); 2651 patternElements.push(element); 2652 } else 2653 elements[elementName] = element; 2654 } 2655 } 2656 } 2657 }; 2658 2659 function setValidElements(valid_elements) { 2660 elements = {}; 2661 patternElements = []; 2662 2663 addValidElements(valid_elements); 2664 2665 each(schemaItems, function(element, name) { 2666 children[name] = element.children; 2667 }); 2668 }; 2669 2670 // Adds custom non HTML elements to the schema 2671 function addCustomElements(custom_elements) { 2672 var customElementRegExp = /^(~)?(.+)$/; 2673 2674 if (custom_elements) { 2675 each(split(custom_elements), function(rule) { 2676 var matches = customElementRegExp.exec(rule), 2677 inline = matches[1] === '~', 2678 cloneName = inline ? 'span' : 'div', 2679 name = matches[2]; 2680 2681 children[name] = children[cloneName]; 2682 customElementsMap[name] = cloneName; 2683 2684 // If it's not marked as inline then add it to valid block elements 2685 if (!inline) 2686 blockElementsMap[name] = {}; 2687 2688 // Add custom elements at span/div positions 2689 each(children, function(element, child) { 2690 if (element[cloneName]) 2691 element[name] = element[cloneName]; 2692 }); 2693 }); 2694 } 2695 }; 2696 2697 // Adds valid children to the schema object 2698 function addValidChildren(valid_children) { 2699 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 2700 2701 if (valid_children) { 2702 each(split(valid_children), function(rule) { 2703 var matches = childRuleRegExp.exec(rule), parent, prefix; 2704 2705 if (matches) { 2706 prefix = matches[1]; 2707 2708 // Add/remove items from default 2709 if (prefix) 2710 parent = children[matches[2]]; 2711 else 2712 parent = children[matches[2]] = {'#comment' : {}}; 2713 2714 parent = children[matches[2]]; 2715 2716 each(split(matches[3], '|'), function(child) { 2717 if (prefix === '-') 2718 delete parent[child]; 2719 else 2720 parent[child] = {}; 2721 }); 2722 } 2723 }); 2724 } 2725 }; 2726 2727 function getElementRule(name) { 2728 var element = elements[name], i; 2729 2730 // Exact match found 2731 if (element) 2732 return element; 2733 2734 // No exact match then try the patterns 2735 i = patternElements.length; 2736 while (i--) { 2737 element = patternElements[i]; 2738 2739 if (element.pattern.test(name)) 2740 return element; 2741 } 2742 }; 2743 2744 if (!settings.valid_elements) { 2745 // No valid elements defined then clone the elements from the schema spec 2746 each(schemaItems, function(element, name) { 2747 elements[name] = { 2748 attributes : element.attributes, 2749 attributesOrder : element.attributesOrder 2750 }; 2751 2752 children[name] = element.children; 2753 }); 2754 2755 // Switch these on HTML4 2756 if (settings.schema != "html5") { 2757 each(split('strong/b,em/i'), function(item) { 2758 item = split(item, '/'); 2759 elements[item[1]].outputName = item[0]; 2760 }); 2761 } 2762 2763 // Add default alt attribute for images 2764 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 2765 2766 // Remove these if they are empty by default 2767 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) { 2768 if (elements[name]) { 2769 elements[name].removeEmpty = true; 2770 } 2771 }); 2772 2773 // Padd these by default 2774 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { 2775 elements[name].paddEmpty = true; 2776 }); 2777 } else 2778 setValidElements(settings.valid_elements); 2779 2780 addCustomElements(settings.custom_elements); 2781 addValidChildren(settings.valid_children); 2782 addValidElements(settings.extended_valid_elements); 2783 2784 // Todo: Remove this when we fix list handling to be valid 2785 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 2786 2787 // Delete invalid elements 2788 if (settings.invalid_elements) { 2789 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { 2790 if (elements[item]) 2791 delete elements[item]; 2792 }); 2793 } 2794 2795 // If the user didn't allow span only allow internal spans 2796 if (!getElementRule('span')) 2797 addValidElements('span[!data-mce-type|*]'); 2798 2799 self.children = children; 2800 2801 self.styles = validStyles; 2802 2803 self.getBoolAttrs = function() { 2804 return boolAttrMap; 2805 }; 2806 2807 self.getBlockElements = function() { 2808 return blockElementsMap; 2809 }; 2810 2811 self.getShortEndedElements = function() { 2812 return shortEndedElementsMap; 2813 }; 2814 2815 self.getSelfClosingElements = function() { 2816 return selfClosingElementsMap; 2817 }; 2818 2819 self.getNonEmptyElements = function() { 2820 return nonEmptyElementsMap; 2821 }; 2822 2823 self.getWhiteSpaceElements = function() { 2824 return whiteSpaceElementsMap; 2825 }; 2826 2827 self.isValidChild = function(name, child) { 2828 var parent = children[name]; 2829 2830 return !!(parent && parent[child]); 2831 }; 2832 2833 self.isValid = function(name, attr) { 2834 var attrPatterns, i, rule = getElementRule(name); 2835 2836 // Check if it's a valid element 2837 if (rule) { 2838 if (attr) { 2839 // Check if attribute name exists 2840 if (rule.attributes[attr]) { 2841 return true; 2842 } 2843 2844 // Check if attribute matches a regexp pattern 2845 attrPatterns = rule.attributePatterns; 2846 if (attrPatterns) { 2847 i = attrPatterns.length; 2848 while (i--) { 2849 if (attrPatterns[i].pattern.test(name)) { 2850 return true; 2851 } 2852 } 2853 } 2854 } else { 2855 return true; 2856 } 2857 } 2858 2859 // No match 2860 return false; 2861 }; 2862 2863 self.getElementRule = getElementRule; 2864 2865 self.getCustomElements = function() { 2866 return customElementsMap; 2867 }; 2868 2869 self.addValidElements = addValidElements; 2870 2871 self.setValidElements = setValidElements; 2872 2873 self.addCustomElements = addCustomElements; 2874 2875 self.addValidChildren = addValidChildren; 2876 }; 2877 })(tinymce); 2878 2879 (function(tinymce) { 2880 tinymce.html.SaxParser = function(settings, schema) { 2881 var self = this, noop = function() {}; 2882 2883 settings = settings || {}; 2884 self.schema = schema = schema || new tinymce.html.Schema(); 2885 2886 if (settings.fix_self_closing !== false) 2887 settings.fix_self_closing = true; 2888 2889 // Add handler functions from settings and setup default handlers 2890 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { 2891 if (name) 2892 self[name] = settings[name] || noop; 2893 }); 2894 2895 self.parse = function(html) { 2896 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, 2897 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, 2898 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, 2899 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; 2900 2901 function processEndTag(name) { 2902 var pos, i; 2903 2904 // Find position of parent of the same type 2905 pos = stack.length; 2906 while (pos--) { 2907 if (stack[pos].name === name) 2908 break; 2909 } 2910 2911 // Found parent 2912 if (pos >= 0) { 2913 // Close all the open elements 2914 for (i = stack.length - 1; i >= pos; i--) { 2915 name = stack[i]; 2916 2917 if (name.valid) 2918 self.end(name.name); 2919 } 2920 2921 // Remove the open elements from the stack 2922 stack.length = pos; 2923 } 2924 }; 2925 2926 function parseAttribute(match, name, value, val2, val3) { 2927 var attrRule, i; 2928 2929 name = name.toLowerCase(); 2930 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 2931 2932 // Validate name and value 2933 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 2934 attrRule = validAttributesMap[name]; 2935 2936 // Find rule by pattern matching 2937 if (!attrRule && validAttributePatterns) { 2938 i = validAttributePatterns.length; 2939 while (i--) { 2940 attrRule = validAttributePatterns[i]; 2941 if (attrRule.pattern.test(name)) 2942 break; 2943 } 2944 2945 // No rule matched 2946 if (i === -1) 2947 attrRule = null; 2948 } 2949 2950 // No attribute rule found 2951 if (!attrRule) 2952 return; 2953 2954 // Validate value 2955 if (attrRule.validValues && !(value in attrRule.validValues)) 2956 return; 2957 } 2958 2959 // Add attribute to list and map 2960 attrList.map[name] = value; 2961 attrList.push({ 2962 name: name, 2963 value: value 2964 }); 2965 }; 2966 2967 // Precompile RegExps and map objects 2968 tokenRegExp = new RegExp('<(?:' + 2969 '(?:!--([\\w\\W]*?)-->)|' + // Comment 2970 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 2971 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 2972 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 2973 '(?:\\/([^>]+)>)|' + // End element 2974 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 2975 ')', 'g'); 2976 2977 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; 2978 specialElements = { 2979 'script' : /<\/script[^>]*>/gi, 2980 'style' : /<\/style[^>]*>/gi, 2981 'noscript' : /<\/noscript[^>]*>/gi 2982 }; 2983 2984 // Setup lookup tables for empty elements and boolean attributes 2985 shortEndedElements = schema.getShortEndedElements(); 2986 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 2987 fillAttrsMap = schema.getBoolAttrs(); 2988 validate = settings.validate; 2989 removeInternalElements = settings.remove_internals; 2990 fixSelfClosing = settings.fix_self_closing; 2991 isIE = tinymce.isIE; 2992 invalidPrefixRegExp = /^:/; 2993 2994 while (matches = tokenRegExp.exec(html)) { 2995 // Text 2996 if (index < matches.index) 2997 self.text(decode(html.substr(index, matches.index - index))); 2998 2999 if (value = matches[6]) { // End element 3000 value = value.toLowerCase(); 3001 3002 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3003 if (isIE && invalidPrefixRegExp.test(value)) 3004 value = value.substr(1); 3005 3006 processEndTag(value); 3007 } else if (value = matches[7]) { // Start element 3008 value = value.toLowerCase(); 3009 3010 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 3011 if (isIE && invalidPrefixRegExp.test(value)) 3012 value = value.substr(1); 3013 3014 isShortEnded = value in shortEndedElements; 3015 3016 // Is self closing tag for example an <li> after an open <li> 3017 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) 3018 processEndTag(value); 3019 3020 // Validate element 3021 if (!validate || (elementRule = schema.getElementRule(value))) { 3022 isValidElement = true; 3023 3024 // Grab attributes map and patters when validation is enabled 3025 if (validate) { 3026 validAttributesMap = elementRule.attributes; 3027 validAttributePatterns = elementRule.attributePatterns; 3028 } 3029 3030 // Parse attributes 3031 if (attribsValue = matches[8]) { 3032 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 3033 3034 // If the element has internal attributes then remove it if we are told to do so 3035 if (isInternalElement && removeInternalElements) 3036 isValidElement = false; 3037 3038 attrList = []; 3039 attrList.map = {}; 3040 3041 attribsValue.replace(attrRegExp, parseAttribute); 3042 } else { 3043 attrList = []; 3044 attrList.map = {}; 3045 } 3046 3047 // Process attributes if validation is enabled 3048 if (validate && !isInternalElement) { 3049 attributesRequired = elementRule.attributesRequired; 3050 attributesDefault = elementRule.attributesDefault; 3051 attributesForced = elementRule.attributesForced; 3052 3053 // Handle forced attributes 3054 if (attributesForced) { 3055 i = attributesForced.length; 3056 while (i--) { 3057 attr = attributesForced[i]; 3058 name = attr.name; 3059 attrValue = attr.value; 3060 3061 if (attrValue === '{$uid}') 3062 attrValue = 'mce_' + idCount++; 3063 3064 attrList.map[name] = attrValue; 3065 attrList.push({name: name, value: attrValue}); 3066 } 3067 } 3068 3069 // Handle default attributes 3070 if (attributesDefault) { 3071 i = attributesDefault.length; 3072 while (i--) { 3073 attr = attributesDefault[i]; 3074 name = attr.name; 3075 3076 if (!(name in attrList.map)) { 3077 attrValue = attr.value; 3078 3079 if (attrValue === '{$uid}') 3080 attrValue = 'mce_' + idCount++; 3081 3082 attrList.map[name] = attrValue; 3083 attrList.push({name: name, value: attrValue}); 3084 } 3085 } 3086 } 3087 3088 // Handle required attributes 3089 if (attributesRequired) { 3090 i = attributesRequired.length; 3091 while (i--) { 3092 if (attributesRequired[i] in attrList.map) 3093 break; 3094 } 3095 3096 // None of the required attributes where found 3097 if (i === -1) 3098 isValidElement = false; 3099 } 3100 3101 // Invalidate element if it's marked as bogus 3102 if (attrList.map['data-mce-bogus']) 3103 isValidElement = false; 3104 } 3105 3106 if (isValidElement) 3107 self.start(value, attrList, isShortEnded); 3108 } else 3109 isValidElement = false; 3110 3111 // Treat script, noscript and style a bit different since they may include code that looks like elements 3112 if (endRegExp = specialElements[value]) { 3113 endRegExp.lastIndex = index = matches.index + matches[0].length; 3114 3115 if (matches = endRegExp.exec(html)) { 3116 if (isValidElement) 3117 text = html.substr(index, matches.index - index); 3118 3119 index = matches.index + matches[0].length; 3120 } else { 3121 text = html.substr(index); 3122 index = html.length; 3123 } 3124 3125 if (isValidElement && text.length > 0) 3126 self.text(text, true); 3127 3128 if (isValidElement) 3129 self.end(value); 3130 3131 tokenRegExp.lastIndex = index; 3132 continue; 3133 } 3134 3135 // Push value on to stack 3136 if (!isShortEnded) { 3137 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) 3138 stack.push({name: value, valid: isValidElement}); 3139 else if (isValidElement) 3140 self.end(value); 3141 } 3142 } else if (value = matches[1]) { // Comment 3143 self.comment(value); 3144 } else if (value = matches[2]) { // CDATA 3145 self.cdata(value); 3146 } else if (value = matches[3]) { // DOCTYPE 3147 self.doctype(value); 3148 } else if (value = matches[4]) { // PI 3149 self.pi(value, matches[5]); 3150 } 3151 3152 index = matches.index + matches[0].length; 3153 } 3154 3155 // Text 3156 if (index < html.length) 3157 self.text(decode(html.substr(index))); 3158 3159 // Close any open elements 3160 for (i = stack.length - 1; i >= 0; i--) { 3161 value = stack[i]; 3162 3163 if (value.valid) 3164 self.end(value.name); 3165 } 3166 }; 3167 } 3168 })(tinymce); 3169 3170 (function(tinymce) { 3171 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 3172 '#text' : 3, 3173 '#comment' : 8, 3174 '#cdata' : 4, 3175 '#pi' : 7, 3176 '#doctype' : 10, 3177 '#document-fragment' : 11 3178 }; 3179 3180 // Walks the tree left/right 3181 function walk(node, root_node, prev) { 3182 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 3183 3184 // Walk into nodes if it has a start 3185 if (node[startName]) 3186 return node[startName]; 3187 3188 // Return the sibling if it has one 3189 if (node !== root_node) { 3190 sibling = node[siblingName]; 3191 3192 if (sibling) 3193 return sibling; 3194 3195 // Walk up the parents to look for siblings 3196 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 3197 sibling = parent[siblingName]; 3198 3199 if (sibling) 3200 return sibling; 3201 } 3202 } 3203 }; 3204 3205 function Node(name, type) { 3206 this.name = name; 3207 this.type = type; 3208 3209 if (type === 1) { 3210 this.attributes = []; 3211 this.attributes.map = {}; 3212 } 3213 } 3214 3215 tinymce.extend(Node.prototype, { 3216 replace : function(node) { 3217 var self = this; 3218 3219 if (node.parent) 3220 node.remove(); 3221 3222 self.insert(node, self); 3223 self.remove(); 3224 3225 return self; 3226 }, 3227 3228 attr : function(name, value) { 3229 var self = this, attrs, i, undef; 3230 3231 if (typeof name !== "string") { 3232 for (i in name) 3233 self.attr(i, name[i]); 3234 3235 return self; 3236 } 3237 3238 if (attrs = self.attributes) { 3239 if (value !== undef) { 3240 // Remove attribute 3241 if (value === null) { 3242 if (name in attrs.map) { 3243 delete attrs.map[name]; 3244 3245 i = attrs.length; 3246 while (i--) { 3247 if (attrs[i].name === name) { 3248 attrs = attrs.splice(i, 1); 3249 return self; 3250 } 3251 } 3252 } 3253 3254 return self; 3255 } 3256 3257 // Set attribute 3258 if (name in attrs.map) { 3259 // Set attribute 3260 i = attrs.length; 3261 while (i--) { 3262 if (attrs[i].name === name) { 3263 attrs[i].value = value; 3264 break; 3265 } 3266 } 3267 } else 3268 attrs.push({name: name, value: value}); 3269 3270 attrs.map[name] = value; 3271 3272 return self; 3273 } else { 3274 return attrs.map[name]; 3275 } 3276 } 3277 }, 3278 3279 clone : function() { 3280 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 3281 3282 // Clone element attributes 3283 if (selfAttrs = self.attributes) { 3284 cloneAttrs = []; 3285 cloneAttrs.map = {}; 3286 3287 for (i = 0, l = selfAttrs.length; i < l; i++) { 3288 selfAttr = selfAttrs[i]; 3289 3290 // Clone everything except id 3291 if (selfAttr.name !== 'id') { 3292 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 3293 cloneAttrs.map[selfAttr.name] = selfAttr.value; 3294 } 3295 } 3296 3297 clone.attributes = cloneAttrs; 3298 } 3299 3300 clone.value = self.value; 3301 clone.shortEnded = self.shortEnded; 3302 3303 return clone; 3304 }, 3305 3306 wrap : function(wrapper) { 3307 var self = this; 3308 3309 self.parent.insert(wrapper, self); 3310 wrapper.append(self); 3311 3312 return self; 3313 }, 3314 3315 unwrap : function() { 3316 var self = this, node, next; 3317 3318 for (node = self.firstChild; node; ) { 3319 next = node.next; 3320 self.insert(node, self, true); 3321 node = next; 3322 } 3323 3324 self.remove(); 3325 }, 3326 3327 remove : function() { 3328 var self = this, parent = self.parent, next = self.next, prev = self.prev; 3329 3330 if (parent) { 3331 if (parent.firstChild === self) { 3332 parent.firstChild = next; 3333 3334 if (next) 3335 next.prev = null; 3336 } else { 3337 prev.next = next; 3338 } 3339 3340 if (parent.lastChild === self) { 3341 parent.lastChild = prev; 3342 3343 if (prev) 3344 prev.next = null; 3345 } else { 3346 next.prev = prev; 3347 } 3348 3349 self.parent = self.next = self.prev = null; 3350 } 3351 3352 return self; 3353 }, 3354 3355 append : function(node) { 3356 var self = this, last; 3357 3358 if (node.parent) 3359 node.remove(); 3360 3361 last = self.lastChild; 3362 if (last) { 3363 last.next = node; 3364 node.prev = last; 3365 self.lastChild = node; 3366 } else 3367 self.lastChild = self.firstChild = node; 3368 3369 node.parent = self; 3370 3371 return node; 3372 }, 3373 3374 insert : function(node, ref_node, before) { 3375 var parent; 3376 3377 if (node.parent) 3378 node.remove(); 3379 3380 parent = ref_node.parent || this; 3381 3382 if (before) { 3383 if (ref_node === parent.firstChild) 3384 parent.firstChild = node; 3385 else 3386 ref_node.prev.next = node; 3387 3388 node.prev = ref_node.prev; 3389 node.next = ref_node; 3390 ref_node.prev = node; 3391 } else { 3392 if (ref_node === parent.lastChild) 3393 parent.lastChild = node; 3394 else 3395 ref_node.next.prev = node; 3396 3397 node.next = ref_node.next; 3398 node.prev = ref_node; 3399 ref_node.next = node; 3400 } 3401 3402 node.parent = parent; 3403 3404 return node; 3405 }, 3406 3407 getAll : function(name) { 3408 var self = this, node, collection = []; 3409 3410 for (node = self.firstChild; node; node = walk(node, self)) { 3411 if (node.name === name) 3412 collection.push(node); 3413 } 3414 3415 return collection; 3416 }, 3417 3418 empty : function() { 3419 var self = this, nodes, i, node; 3420 3421 // Remove all children 3422 if (self.firstChild) { 3423 nodes = []; 3424 3425 // Collect the children 3426 for (node = self.firstChild; node; node = walk(node, self)) 3427 nodes.push(node); 3428 3429 // Remove the children 3430 i = nodes.length; 3431 while (i--) { 3432 node = nodes[i]; 3433 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 3434 } 3435 } 3436 3437 self.firstChild = self.lastChild = null; 3438 3439 return self; 3440 }, 3441 3442 isEmpty : function(elements) { 3443 var self = this, node = self.firstChild, i, name; 3444 3445 if (node) { 3446 do { 3447 if (node.type === 1) { 3448 // Ignore bogus elements 3449 if (node.attributes.map['data-mce-bogus']) 3450 continue; 3451 3452 // Keep empty elements like <img /> 3453 if (elements[node.name]) 3454 return false; 3455 3456 // Keep elements with data attributes or name attribute like <a name="1"></a> 3457 i = node.attributes.length; 3458 while (i--) { 3459 name = node.attributes[i].name; 3460 if (name === "name" || name.indexOf('data-') === 0) 3461 return false; 3462 } 3463 } 3464 3465 // Keep comments 3466 if (node.type === 8) 3467 return false; 3468 3469 // Keep non whitespace text nodes 3470 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) 3471 return false; 3472 } while (node = walk(node, self)); 3473 } 3474 3475 return true; 3476 }, 3477 3478 walk : function(prev) { 3479 return walk(this, null, prev); 3480 } 3481 }); 3482 3483 tinymce.extend(Node, { 3484 create : function(name, attrs) { 3485 var node, attrName; 3486 3487 // Create node 3488 node = new Node(name, typeLookup[name] || 1); 3489 3490 // Add attributes if needed 3491 if (attrs) { 3492 for (attrName in attrs) 3493 node.attr(attrName, attrs[attrName]); 3494 } 3495 3496 return node; 3497 } 3498 }); 3499 3500 tinymce.html.Node = Node; 3501 })(tinymce); 3502 3503 (function(tinymce) { 3504 var Node = tinymce.html.Node; 3505 3506 tinymce.html.DomParser = function(settings, schema) { 3507 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 3508 3509 settings = settings || {}; 3510 settings.validate = "validate" in settings ? settings.validate : true; 3511 settings.root_name = settings.root_name || 'body'; 3512 self.schema = schema = schema || new tinymce.html.Schema(); 3513 3514 function fixInvalidChildren(nodes) { 3515 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, 3516 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; 3517 3518 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); 3519 nonEmptyElements = schema.getNonEmptyElements(); 3520 3521 for (ni = 0; ni < nodes.length; ni++) { 3522 node = nodes[ni]; 3523 3524 // Already removed 3525 if (!node.parent) 3526 continue; 3527 3528 // Get list of all parent nodes until we find a valid parent to stick the child into 3529 parents = [node]; 3530 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) 3531 parents.push(parent); 3532 3533 // Found a suitable parent 3534 if (parent && parents.length > 1) { 3535 // Reverse the array since it makes looping easier 3536 parents.reverse(); 3537 3538 // Clone the related parent and insert that after the moved node 3539 newParent = currentNode = self.filterNode(parents[0].clone()); 3540 3541 // Start cloning and moving children on the left side of the target node 3542 for (i = 0; i < parents.length - 1; i++) { 3543 if (schema.isValidChild(currentNode.name, parents[i].name)) { 3544 tempNode = self.filterNode(parents[i].clone()); 3545 currentNode.append(tempNode); 3546 } else 3547 tempNode = currentNode; 3548 3549 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { 3550 nextNode = childNode.next; 3551 tempNode.append(childNode); 3552 childNode = nextNode; 3553 } 3554 3555 currentNode = tempNode; 3556 } 3557 3558 if (!newParent.isEmpty(nonEmptyElements)) { 3559 parent.insert(newParent, parents[0], true); 3560 parent.insert(node, newParent); 3561 } else { 3562 parent.insert(node, parents[0], true); 3563 } 3564 3565 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 3566 parent = parents[0]; 3567 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 3568 parent.empty().remove(); 3569 } 3570 } else if (node.parent) { 3571 // If it's an LI try to find a UL/OL for it or wrap it 3572 if (node.name === 'li') { 3573 sibling = node.prev; 3574 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3575 sibling.append(node); 3576 continue; 3577 } 3578 3579 sibling = node.next; 3580 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 3581 sibling.insert(node, sibling.firstChild, true); 3582 continue; 3583 } 3584 3585 node.wrap(self.filterNode(new Node('ul', 1))); 3586 continue; 3587 } 3588 3589 // Try wrapping the element in a DIV 3590 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 3591 node.wrap(self.filterNode(new Node('div', 1))); 3592 } else { 3593 // We failed wrapping it, then remove or unwrap it 3594 if (node.name === 'style' || node.name === 'script') 3595 node.empty().remove(); 3596 else 3597 node.unwrap(); 3598 } 3599 } 3600 } 3601 }; 3602 3603 self.filterNode = function(node) { 3604 var i, name, list; 3605 3606 // Run element filters 3607 if (name in nodeFilters) { 3608 list = matchedNodes[name]; 3609 3610 if (list) 3611 list.push(node); 3612 else 3613 matchedNodes[name] = [node]; 3614 } 3615 3616 // Run attribute filters 3617 i = attributeFilters.length; 3618 while (i--) { 3619 name = attributeFilters[i].name; 3620 3621 if (name in node.attributes.map) { 3622 list = matchedAttributes[name]; 3623 3624 if (list) 3625 list.push(node); 3626 else 3627 matchedAttributes[name] = [node]; 3628 } 3629 } 3630 3631 return node; 3632 }; 3633 3634 self.addNodeFilter = function(name, callback) { 3635 tinymce.each(tinymce.explode(name), function(name) { 3636 var list = nodeFilters[name]; 3637 3638 if (!list) 3639 nodeFilters[name] = list = []; 3640 3641 list.push(callback); 3642 }); 3643 }; 3644 3645 self.addAttributeFilter = function(name, callback) { 3646 tinymce.each(tinymce.explode(name), function(name) { 3647 var i; 3648 3649 for (i = 0; i < attributeFilters.length; i++) { 3650 if (attributeFilters[i].name === name) { 3651 attributeFilters[i].callbacks.push(callback); 3652 return; 3653 } 3654 } 3655 3656 attributeFilters.push({name: name, callbacks: [callback]}); 3657 }); 3658 }; 3659 3660 self.parse = function(html, args) { 3661 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, 3662 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement, 3663 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; 3664 3665 args = args || {}; 3666 matchedNodes = {}; 3667 matchedAttributes = {}; 3668 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 3669 nonEmptyElements = schema.getNonEmptyElements(); 3670 children = schema.children; 3671 validate = settings.validate; 3672 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 3673 3674 whiteSpaceElements = schema.getWhiteSpaceElements(); 3675 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 3676 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 3677 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 3678 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 3679 3680 function addRootBlocks() { 3681 var node = rootNode.firstChild, next, rootBlockNode; 3682 3683 while (node) { 3684 next = node.next; 3685 3686 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { 3687 if (!rootBlockNode) { 3688 // Create a new root block element 3689 rootBlockNode = createNode(rootBlockName, 1); 3690 rootNode.insert(rootBlockNode, node); 3691 rootBlockNode.append(node); 3692 } else 3693 rootBlockNode.append(node); 3694 } else { 3695 rootBlockNode = null; 3696 } 3697 3698 node = next; 3699 }; 3700 }; 3701 3702 function createNode(name, type) { 3703 var node = new Node(name, type), list; 3704 3705 if (name in nodeFilters) { 3706 list = matchedNodes[name]; 3707 3708 if (list) 3709 list.push(node); 3710 else 3711 matchedNodes[name] = [node]; 3712 } 3713 3714 return node; 3715 }; 3716 3717 function removeWhitespaceBefore(node) { 3718 var textNode, textVal, sibling; 3719 3720 for (textNode = node.prev; textNode && textNode.type === 3; ) { 3721 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 3722 3723 if (textVal.length > 0) { 3724 textNode.value = textVal; 3725 textNode = textNode.prev; 3726 } else { 3727 sibling = textNode.prev; 3728 textNode.remove(); 3729 textNode = sibling; 3730 } 3731 } 3732 }; 3733 3734 function cloneAndExcludeBlocks(input) { 3735 var name, output = {}; 3736 3737 for (name in input) { 3738 if (name !== 'li' && name != 'p') { 3739 output[name] = input[name]; 3740 } 3741 } 3742 3743 return output; 3744 }; 3745 3746 parser = new tinymce.html.SaxParser({ 3747 validate : validate, 3748 3749 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 3750 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 3751 3752 cdata: function(text) { 3753 node.append(createNode('#cdata', 4)).value = text; 3754 }, 3755 3756 text: function(text, raw) { 3757 var textNode; 3758 3759 // Trim all redundant whitespace on non white space elements 3760 if (!isInWhiteSpacePreservedElement) { 3761 text = text.replace(allWhiteSpaceRegExp, ' '); 3762 3763 if (node.lastChild && blockElements[node.lastChild.name]) 3764 text = text.replace(startWhiteSpaceRegExp, ''); 3765 } 3766 3767 // Do we need to create the node 3768 if (text.length !== 0) { 3769 textNode = createNode('#text', 3); 3770 textNode.raw = !!raw; 3771 node.append(textNode).value = text; 3772 } 3773 }, 3774 3775 comment: function(text) { 3776 node.append(createNode('#comment', 8)).value = text; 3777 }, 3778 3779 pi: function(name, text) { 3780 node.append(createNode(name, 7)).value = text; 3781 removeWhitespaceBefore(node); 3782 }, 3783 3784 doctype: function(text) { 3785 var newNode; 3786 3787 newNode = node.append(createNode('#doctype', 10)); 3788 newNode.value = text; 3789 removeWhitespaceBefore(node); 3790 }, 3791 3792 start: function(name, attrs, empty) { 3793 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; 3794 3795 elementRule = validate ? schema.getElementRule(name) : {}; 3796 if (elementRule) { 3797 newNode = createNode(elementRule.outputName || name, 1); 3798 newNode.attributes = attrs; 3799 newNode.shortEnded = empty; 3800 3801 node.append(newNode); 3802 3803 // Check if node is valid child of the parent node is the child is 3804 // unknown we don't collect it since it's probably a custom element 3805 parent = children[node.name]; 3806 if (parent && children[newNode.name] && !parent[newNode.name]) 3807 invalidChildren.push(newNode); 3808 3809 attrFiltersLen = attributeFilters.length; 3810 while (attrFiltersLen--) { 3811 attrName = attributeFilters[attrFiltersLen].name; 3812 3813 if (attrName in attrs.map) { 3814 list = matchedAttributes[attrName]; 3815 3816 if (list) 3817 list.push(newNode); 3818 else 3819 matchedAttributes[attrName] = [newNode]; 3820 } 3821 } 3822 3823 // Trim whitespace before block 3824 if (blockElements[name]) 3825 removeWhitespaceBefore(newNode); 3826 3827 // Change current node if the element wasn't empty i.e not <br /> or <img /> 3828 if (!empty) 3829 node = newNode; 3830 3831 // Check if we are inside a whitespace preserved element 3832 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3833 isInWhiteSpacePreservedElement = true; 3834 } 3835 } 3836 }, 3837 3838 end: function(name) { 3839 var textNode, elementRule, text, sibling, tempNode; 3840 3841 elementRule = validate ? schema.getElementRule(name) : {}; 3842 if (elementRule) { 3843 if (blockElements[name]) { 3844 if (!isInWhiteSpacePreservedElement) { 3845 // Trim whitespace of the first node in a block 3846 textNode = node.firstChild; 3847 if (textNode && textNode.type === 3) { 3848 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3849 3850 // Any characters left after trim or should we remove it 3851 if (text.length > 0) { 3852 textNode.value = text; 3853 textNode = textNode.next; 3854 } else { 3855 sibling = textNode.next; 3856 textNode.remove(); 3857 textNode = sibling; 3858 } 3859 3860 // Remove any pure whitespace siblings 3861 while (textNode && textNode.type === 3) { 3862 text = textNode.value; 3863 sibling = textNode.next; 3864 3865 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3866 textNode.remove(); 3867 textNode = sibling; 3868 } 3869 3870 textNode = sibling; 3871 } 3872 } 3873 3874 // Trim whitespace of the last node in a block 3875 textNode = node.lastChild; 3876 if (textNode && textNode.type === 3) { 3877 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 3878 3879 // Any characters left after trim or should we remove it 3880 if (text.length > 0) { 3881 textNode.value = text; 3882 textNode = textNode.prev; 3883 } else { 3884 sibling = textNode.prev; 3885 textNode.remove(); 3886 textNode = sibling; 3887 } 3888 3889 // Remove any pure whitespace siblings 3890 while (textNode && textNode.type === 3) { 3891 text = textNode.value; 3892 sibling = textNode.prev; 3893 3894 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 3895 textNode.remove(); 3896 textNode = sibling; 3897 } 3898 3899 textNode = sibling; 3900 } 3901 } 3902 } 3903 3904 // Trim start white space 3905 textNode = node.prev; 3906 if (textNode && textNode.type === 3) { 3907 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 3908 3909 if (text.length > 0) 3910 textNode.value = text; 3911 else 3912 textNode.remove(); 3913 } 3914 } 3915 3916 // Check if we exited a whitespace preserved element 3917 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 3918 isInWhiteSpacePreservedElement = false; 3919 } 3920 3921 // Handle empty nodes 3922 if (elementRule.removeEmpty || elementRule.paddEmpty) { 3923 if (node.isEmpty(nonEmptyElements)) { 3924 if (elementRule.paddEmpty) 3925 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 3926 else { 3927 // Leave nodes that have a name like <a name="name"> 3928 if (!node.attributes.map.name && !node.attributes.map.id) { 3929 tempNode = node.parent; 3930 node.empty().remove(); 3931 node = tempNode; 3932 return; 3933 } 3934 } 3935 } 3936 } 3937 3938 node = node.parent; 3939 } 3940 } 3941 }, schema); 3942 3943 rootNode = node = new Node(args.context || settings.root_name, 11); 3944 3945 parser.parse(html); 3946 3947 // Fix invalid children or report invalid children in a contextual parsing 3948 if (validate && invalidChildren.length) { 3949 if (!args.context) 3950 fixInvalidChildren(invalidChildren); 3951 else 3952 args.invalid = true; 3953 } 3954 3955 // Wrap nodes in the root into block elements if the root is body 3956 if (rootBlockName && rootNode.name == 'body') 3957 addRootBlocks(); 3958 3959 // Run filters only when the contents is valid 3960 if (!args.invalid) { 3961 // Run node filters 3962 for (name in matchedNodes) { 3963 list = nodeFilters[name]; 3964 nodes = matchedNodes[name]; 3965 3966 // Remove already removed children 3967 fi = nodes.length; 3968 while (fi--) { 3969 if (!nodes[fi].parent) 3970 nodes.splice(fi, 1); 3971 } 3972 3973 for (i = 0, l = list.length; i < l; i++) 3974 list[i](nodes, name, args); 3975 } 3976 3977 // Run attribute filters 3978 for (i = 0, l = attributeFilters.length; i < l; i++) { 3979 list = attributeFilters[i]; 3980 3981 if (list.name in matchedAttributes) { 3982 nodes = matchedAttributes[list.name]; 3983 3984 // Remove already removed children 3985 fi = nodes.length; 3986 while (fi--) { 3987 if (!nodes[fi].parent) 3988 nodes.splice(fi, 1); 3989 } 3990 3991 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) 3992 list.callbacks[fi](nodes, list.name, args); 3993 } 3994 } 3995 } 3996 3997 return rootNode; 3998 }; 3999 4000 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 4001 // make it possible to place the caret inside empty blocks. This logic tries to remove 4002 // these elements and keep br elements that where intended to be there intact 4003 if (settings.remove_trailing_brs) { 4004 self.addNodeFilter('br', function(nodes, name) { 4005 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()), 4006 nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 4007 4008 // Remove brs from body element as well 4009 blockElements.body = 1; 4010 4011 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 4012 for (i = 0; i < l; i++) { 4013 node = nodes[i]; 4014 parent = node.parent; 4015 4016 if (blockElements[node.parent.name] && node === parent.lastChild) { 4017 // Loop all nodes to the left of the current node and check for other BR elements 4018 // excluding bookmarks since they are invisible 4019 prev = node.prev; 4020 while (prev) { 4021 prevName = prev.name; 4022 4023 // Ignore bookmarks 4024 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 4025 // Found a non BR element 4026 if (prevName !== "br") 4027 break; 4028 4029 // Found another br it's a <br><br> structure then don't remove anything 4030 if (prevName === 'br') { 4031 node = null; 4032 break; 4033 } 4034 } 4035 4036 prev = prev.prev; 4037 } 4038 4039 if (node) { 4040 node.remove(); 4041 4042 // Is the parent to be considered empty after we removed the BR 4043 if (parent.isEmpty(nonEmptyElements)) { 4044 elementRule = schema.getElementRule(parent.name); 4045 4046 // Remove or padd the element depending on schema rule 4047 if (elementRule) { 4048 if (elementRule.removeEmpty) 4049 parent.remove(); 4050 else if (elementRule.paddEmpty) 4051 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; 4052 } 4053 } 4054 } 4055 } else { 4056 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 4057 lastParent = node; 4058 while (parent.firstChild === lastParent && parent.lastChild === lastParent) { 4059 lastParent = parent; 4060 4061 if (blockElements[parent.name]) { 4062 break; 4063 } 4064 4065 parent = parent.parent; 4066 } 4067 4068 if (lastParent === parent) { 4069 textNode = new tinymce.html.Node('#text', 3); 4070 textNode.value = '\u00a0'; 4071 node.replace(textNode); 4072 } 4073 } 4074 } 4075 }); 4076 } 4077 4078 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 4079 if (!settings.allow_html_in_named_anchor) { 4080 self.addAttributeFilter('id,name', function(nodes, name) { 4081 var i = nodes.length, sibling, prevSibling, parent, node; 4082 4083 while (i--) { 4084 node = nodes[i]; 4085 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 4086 parent = node.parent; 4087 4088 // Move children after current node 4089 sibling = node.lastChild; 4090 do { 4091 prevSibling = sibling.prev; 4092 parent.insert(sibling, node); 4093 sibling = prevSibling; 4094 } while (sibling); 4095 } 4096 } 4097 }); 4098 } 4099 } 4100 })(tinymce); 4101 4102 tinymce.html.Writer = function(settings) { 4103 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 4104 4105 settings = settings || {}; 4106 indent = settings.indent; 4107 indentBefore = tinymce.makeMap(settings.indent_before || ''); 4108 indentAfter = tinymce.makeMap(settings.indent_after || ''); 4109 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 4110 htmlOutput = settings.element_format == "html"; 4111 4112 return { 4113 start: function(name, attrs, empty) { 4114 var i, l, attr, value; 4115 4116 if (indent && indentBefore[name] && html.length > 0) { 4117 value = html[html.length - 1]; 4118 4119 if (value.length > 0 && value !== '\n') 4120 html.push('\n'); 4121 } 4122 4123 html.push('<', name); 4124 4125 if (attrs) { 4126 for (i = 0, l = attrs.length; i < l; i++) { 4127 attr = attrs[i]; 4128 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 4129 } 4130 } 4131 4132 if (!empty || htmlOutput) 4133 html[html.length] = '>'; 4134 else 4135 html[html.length] = ' />'; 4136 4137 if (empty && indent && indentAfter[name] && html.length > 0) { 4138 value = html[html.length - 1]; 4139 4140 if (value.length > 0 && value !== '\n') 4141 html.push('\n'); 4142 } 4143 }, 4144 4145 end: function(name) { 4146 var value; 4147 4148 /*if (indent && indentBefore[name] && html.length > 0) { 4149 value = html[html.length - 1]; 4150 4151 if (value.length > 0 && value !== '\n') 4152 html.push('\n'); 4153 }*/ 4154 4155 html.push('</', name, '>'); 4156 4157 if (indent && indentAfter[name] && html.length > 0) { 4158 value = html[html.length - 1]; 4159 4160 if (value.length > 0 && value !== '\n') 4161 html.push('\n'); 4162 } 4163 }, 4164 4165 text: function(text, raw) { 4166 if (text.length > 0) 4167 html[html.length] = raw ? text : encode(text); 4168 }, 4169 4170 cdata: function(text) { 4171 html.push('<![CDATA[', text, ']]>'); 4172 }, 4173 4174 comment: function(text) { 4175 html.push('<!--', text, '-->'); 4176 }, 4177 4178 pi: function(name, text) { 4179 if (text) 4180 html.push('<?', name, ' ', text, '?>'); 4181 else 4182 html.push('<?', name, '?>'); 4183 4184 if (indent) 4185 html.push('\n'); 4186 }, 4187 4188 doctype: function(text) { 4189 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 4190 }, 4191 4192 reset: function() { 4193 html.length = 0; 4194 }, 4195 4196 getContent: function() { 4197 return html.join('').replace(/\n$/, ''); 4198 } 4199 }; 4200 }; 4201 4202 (function(tinymce) { 4203 tinymce.html.Serializer = function(settings, schema) { 4204 var self = this, writer = new tinymce.html.Writer(settings); 4205 4206 settings = settings || {}; 4207 settings.validate = "validate" in settings ? settings.validate : true; 4208 4209 self.schema = schema = schema || new tinymce.html.Schema(); 4210 self.writer = writer; 4211 4212 self.serialize = function(node) { 4213 var handlers, validate; 4214 4215 validate = settings.validate; 4216 4217 handlers = { 4218 // #text 4219 3: function(node, raw) { 4220 writer.text(node.value, node.raw); 4221 }, 4222 4223 // #comment 4224 8: function(node) { 4225 writer.comment(node.value); 4226 }, 4227 4228 // Processing instruction 4229 7: function(node) { 4230 writer.pi(node.name, node.value); 4231 }, 4232 4233 // Doctype 4234 10: function(node) { 4235 writer.doctype(node.value); 4236 }, 4237 4238 // CDATA 4239 4: function(node) { 4240 writer.cdata(node.value); 4241 }, 4242 4243 // Document fragment 4244 11: function(node) { 4245 if ((node = node.firstChild)) { 4246 do { 4247 walk(node); 4248 } while (node = node.next); 4249 } 4250 } 4251 }; 4252 4253 writer.reset(); 4254 4255 function walk(node) { 4256 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 4257 4258 if (!handler) { 4259 name = node.name; 4260 isEmpty = node.shortEnded; 4261 attrs = node.attributes; 4262 4263 // Sort attributes 4264 if (validate && attrs && attrs.length > 1) { 4265 sortedAttrs = []; 4266 sortedAttrs.map = {}; 4267 4268 elementRule = schema.getElementRule(node.name); 4269 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 4270 attrName = elementRule.attributesOrder[i]; 4271 4272 if (attrName in attrs.map) { 4273 attrValue = attrs.map[attrName]; 4274 sortedAttrs.map[attrName] = attrValue; 4275 sortedAttrs.push({name: attrName, value: attrValue}); 4276 } 4277 } 4278 4279 for (i = 0, l = attrs.length; i < l; i++) { 4280 attrName = attrs[i].name; 4281 4282 if (!(attrName in sortedAttrs.map)) { 4283 attrValue = attrs.map[attrName]; 4284 sortedAttrs.map[attrName] = attrValue; 4285 sortedAttrs.push({name: attrName, value: attrValue}); 4286 } 4287 } 4288 4289 attrs = sortedAttrs; 4290 } 4291 4292 writer.start(node.name, attrs, isEmpty); 4293 4294 if (!isEmpty) { 4295 if ((node = node.firstChild)) { 4296 do { 4297 walk(node); 4298 } while (node = node.next); 4299 } 4300 4301 writer.end(name); 4302 } 4303 } else 4304 handler(node); 4305 } 4306 4307 // Serialize element and treat all non elements as fragments 4308 if (node.type == 1 && !settings.inner) 4309 walk(node); 4310 else 4311 handlers[11](node); 4312 4313 return writer.getContent(); 4314 }; 4315 } 4316 })(tinymce); 4317 4318 // JSLint defined globals 4319 /*global tinymce:false, window:false */ 4320 4321 tinymce.dom = {}; 4322 4323 (function(namespace, expando) { 4324 var w3cEventModel = !!document.addEventListener; 4325 4326 function addEvent(target, name, callback, capture) { 4327 if (target.addEventListener) { 4328 target.addEventListener(name, callback, capture || false); 4329 } else if (target.attachEvent) { 4330 target.attachEvent('on' + name, callback); 4331 } 4332 } 4333 4334 function removeEvent(target, name, callback, capture) { 4335 if (target.removeEventListener) { 4336 target.removeEventListener(name, callback, capture || false); 4337 } else if (target.detachEvent) { 4338 target.detachEvent('on' + name, callback); 4339 } 4340 } 4341 4342 function fix(original_event, data) { 4343 var name, event = data || {}; 4344 4345 // Dummy function that gets replaced on the delegation state functions 4346 function returnFalse() { 4347 return false; 4348 } 4349 4350 // Dummy function that gets replaced on the delegation state functions 4351 function returnTrue() { 4352 return true; 4353 } 4354 4355 // Copy all properties from the original event 4356 for (name in original_event) { 4357 // layerX/layerY is deprecated in Chrome and produces a warning 4358 if (name !== "layerX" && name !== "layerY") { 4359 event[name] = original_event[name]; 4360 } 4361 } 4362 4363 // Normalize target IE uses srcElement 4364 if (!event.target) { 4365 event.target = event.srcElement || document; 4366 } 4367 4368 // Add preventDefault method 4369 event.preventDefault = function() { 4370 event.isDefaultPrevented = returnTrue; 4371 4372 // Execute preventDefault on the original event object 4373 if (original_event) { 4374 if (original_event.preventDefault) { 4375 original_event.preventDefault(); 4376 } else { 4377 original_event.returnValue = false; // IE 4378 } 4379 } 4380 }; 4381 4382 // Add stopPropagation 4383 event.stopPropagation = function() { 4384 event.isPropagationStopped = returnTrue; 4385 4386 // Execute stopPropagation on the original event object 4387 if (original_event) { 4388 if (original_event.stopPropagation) { 4389 original_event.stopPropagation(); 4390 } else { 4391 original_event.cancelBubble = true; // IE 4392 } 4393 } 4394 }; 4395 4396 // Add stopImmediatePropagation 4397 event.stopImmediatePropagation = function() { 4398 event.isImmediatePropagationStopped = returnTrue; 4399 event.stopPropagation(); 4400 }; 4401 4402 // Add event delegation states 4403 if (!event.isDefaultPrevented) { 4404 event.isDefaultPrevented = returnFalse; 4405 event.isPropagationStopped = returnFalse; 4406 event.isImmediatePropagationStopped = returnFalse; 4407 } 4408 4409 return event; 4410 } 4411 4412 function bindOnReady(win, callback, event_utils) { 4413 var doc = win.document, event = {type: 'ready'}; 4414 4415 // Gets called when the DOM is ready 4416 function readyHandler() { 4417 if (!event_utils.domLoaded) { 4418 event_utils.domLoaded = true; 4419 callback(event); 4420 } 4421 } 4422 4423 // Use W3C method 4424 if (w3cEventModel) { 4425 addEvent(win, 'DOMContentLoaded', readyHandler); 4426 } else { 4427 // Use IE method 4428 addEvent(doc, "readystatechange", function() { 4429 if (doc.readyState === "complete") { 4430 removeEvent(doc, "readystatechange", arguments.callee); 4431 readyHandler(); 4432 } 4433 }); 4434 4435 // Wait until we can scroll, when we can the DOM is initialized 4436 if (doc.documentElement.doScroll && win === win.top) { 4437 (function() { 4438 try { 4439 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 4440 // http://javascript.nwbox.com/IEContentLoaded/ 4441 doc.documentElement.doScroll("left"); 4442 } catch (ex) { 4443 setTimeout(arguments.callee, 0); 4444 return; 4445 } 4446 4447 readyHandler(); 4448 })(); 4449 } 4450 } 4451 4452 // Fallback if any of the above methods should fail for some odd reason 4453 addEvent(win, 'load', readyHandler); 4454 } 4455 4456 function EventUtils(proxy) { 4457 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 4458 4459 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 4460 hasFocusIn = "onfocusin" in document.documentElement; 4461 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 4462 count = 1; 4463 4464 // State if the DOMContentLoaded was executed or not 4465 self.domLoaded = false; 4466 self.events = events; 4467 4468 function executeHandlers(evt, id) { 4469 var callbackList, i, l, callback; 4470 4471 callbackList = events[id][evt.type]; 4472 if (callbackList) { 4473 for (i = 0, l = callbackList.length; i < l; i++) { 4474 callback = callbackList[i]; 4475 4476 // Check if callback exists might be removed if a unbind is called inside the callback 4477 if (callback && callback.func.call(callback.scope, evt) === false) { 4478 evt.preventDefault(); 4479 } 4480 4481 // Should we stop propagation to immediate listeners 4482 if (evt.isImmediatePropagationStopped()) { 4483 return; 4484 } 4485 } 4486 } 4487 } 4488 4489 self.bind = function(target, names, callback, scope) { 4490 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 4491 4492 // Native event handler function patches the event and executes the callbacks for the expando 4493 function defaultNativeHandler(evt) { 4494 executeHandlers(fix(evt || win.event), id); 4495 } 4496 4497 // Don't bind to text nodes or comments 4498 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4499 return; 4500 } 4501 4502 // Create or get events id for the target 4503 if (!target[expando]) { 4504 id = count++; 4505 target[expando] = id; 4506 events[id] = {}; 4507 } else { 4508 id = target[expando]; 4509 4510 if (!events[id]) { 4511 events[id] = {}; 4512 } 4513 } 4514 4515 // Setup the specified scope or use the target as a default 4516 scope = scope || target; 4517 4518 // Split names and bind each event, enables you to bind multiple events with one call 4519 names = names.split(' '); 4520 i = names.length; 4521 while (i--) { 4522 name = names[i]; 4523 nativeHandler = defaultNativeHandler; 4524 fakeName = capture = false; 4525 4526 // Use ready instead of DOMContentLoaded 4527 if (name === "DOMContentLoaded") { 4528 name = "ready"; 4529 } 4530 4531 // DOM is already ready 4532 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") { 4533 self.domLoaded = true; 4534 callback.call(scope, fix({type: name})); 4535 continue; 4536 } 4537 4538 // Handle mouseenter/mouseleaver 4539 if (!hasMouseEnterLeave) { 4540 fakeName = mouseEnterLeave[name]; 4541 4542 if (fakeName) { 4543 nativeHandler = function(evt) { 4544 var current, related; 4545 4546 current = evt.currentTarget; 4547 related = evt.relatedTarget; 4548 4549 // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element 4550 if (related && current.contains) { 4551 // Use contains for performance 4552 related = current.contains(related); 4553 } else { 4554 while (related && related !== current) { 4555 related = related.parentNode; 4556 } 4557 } 4558 4559 // Fire fake event 4560 if (!related) { 4561 evt = fix(evt || win.event); 4562 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 4563 evt.target = current; 4564 executeHandlers(evt, id); 4565 } 4566 }; 4567 } 4568 } 4569 4570 // Fake bubbeling of focusin/focusout 4571 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 4572 capture = true; 4573 fakeName = name === "focusin" ? "focus" : "blur"; 4574 nativeHandler = function(evt) { 4575 evt = fix(evt || win.event); 4576 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 4577 executeHandlers(evt, id); 4578 }; 4579 } 4580 4581 // Setup callback list and bind native event 4582 callbackList = events[id][name]; 4583 if (!callbackList) { 4584 events[id][name] = callbackList = [{func: callback, scope: scope}]; 4585 callbackList.fakeName = fakeName; 4586 callbackList.capture = capture; 4587 4588 // Add the nativeHandler to the callback list so that we can later unbind it 4589 callbackList.nativeHandler = nativeHandler; 4590 if (!w3cEventModel) { 4591 callbackList.proxyHandler = proxy(id); 4592 } 4593 4594 // Check if the target has native events support 4595 if (name === "ready") { 4596 bindOnReady(target, nativeHandler, self); 4597 } else { 4598 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture); 4599 } 4600 } else { 4601 // If it already has an native handler then just push the callback 4602 callbackList.push({func: callback, scope: scope}); 4603 } 4604 } 4605 4606 target = callbackList = 0; // Clean memory for IE 4607 4608 return callback; 4609 }; 4610 4611 self.unbind = function(target, names, callback) { 4612 var id, callbackList, i, ci, name, eventMap; 4613 4614 // Don't bind to text nodes or comments 4615 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4616 return self; 4617 } 4618 4619 // Unbind event or events if the target has the expando 4620 id = target[expando]; 4621 if (id) { 4622 eventMap = events[id]; 4623 4624 // Specific callback 4625 if (names) { 4626 names = names.split(' '); 4627 i = names.length; 4628 while (i--) { 4629 name = names[i]; 4630 callbackList = eventMap[name]; 4631 4632 // Unbind the event if it exists in the map 4633 if (callbackList) { 4634 // Remove specified callback 4635 if (callback) { 4636 ci = callbackList.length; 4637 while (ci--) { 4638 if (callbackList[ci].func === callback) { 4639 callbackList.splice(ci, 1); 4640 } 4641 } 4642 } 4643 4644 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 4645 if (!callback || callbackList.length === 0) { 4646 delete eventMap[name]; 4647 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4648 } 4649 } 4650 } 4651 } else { 4652 // All events for a specific element 4653 for (name in eventMap) { 4654 callbackList = eventMap[name]; 4655 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture); 4656 } 4657 4658 eventMap = {}; 4659 } 4660 4661 // Check if object is empty, if it isn't then we won't remove the expando map 4662 for (name in eventMap) { 4663 return self; 4664 } 4665 4666 // Delete event object 4667 delete events[id]; 4668 4669 // Remove expando from target 4670 try { 4671 // IE will fail here since it can't delete properties from window 4672 delete target[expando]; 4673 } catch (ex) { 4674 // IE will set it to null 4675 target[expando] = null; 4676 } 4677 } 4678 4679 return self; 4680 }; 4681 4682 self.fire = function(target, name, args) { 4683 var id, event; 4684 4685 // Don't bind to text nodes or comments 4686 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4687 return self; 4688 } 4689 4690 // Build event object by patching the args 4691 event = fix(null, args); 4692 event.type = name; 4693 4694 do { 4695 // Found an expando that means there is listeners to execute 4696 id = target[expando]; 4697 if (id) { 4698 executeHandlers(event, id); 4699 } 4700 4701 // Walk up the DOM 4702 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 4703 } while (target && !event.isPropagationStopped()); 4704 4705 return self; 4706 }; 4707 4708 self.clean = function(target) { 4709 var i, children, unbind = self.unbind; 4710 4711 // Don't bind to text nodes or comments 4712 if (!target || target.nodeType === 3 || target.nodeType === 8) { 4713 return self; 4714 } 4715 4716 // Unbind any element on the specificed target 4717 if (target[expando]) { 4718 unbind(target); 4719 } 4720 4721 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 4722 if (!target.getElementsByTagName) { 4723 target = target.document; 4724 } 4725 4726 // Remove events from each child element 4727 if (target && target.getElementsByTagName) { 4728 unbind(target); 4729 4730 children = target.getElementsByTagName('*'); 4731 i = children.length; 4732 while (i--) { 4733 target = children[i]; 4734 4735 if (target[expando]) { 4736 unbind(target); 4737 } 4738 } 4739 } 4740 4741 return self; 4742 }; 4743 4744 self.callNativeHandler = function(id, evt) { 4745 if (events) { 4746 events[id][evt.type].nativeHandler(evt); 4747 } 4748 }; 4749 4750 self.destory = function() { 4751 events = {}; 4752 }; 4753 4754 // Legacy function calls 4755 4756 self.add = function(target, events, func, scope) { 4757 // Old API supported direct ID assignment 4758 if (typeof(target) === "string") { 4759 target = document.getElementById(target); 4760 } 4761 4762 // Old API supported multiple targets 4763 if (target && target instanceof Array) { 4764 var i = target.length; 4765 4766 while (i--) { 4767 self.add(target[i], events, func, scope); 4768 } 4769 4770 return; 4771 } 4772 4773 // Old API called ready init 4774 if (events === "init") { 4775 events = "ready"; 4776 } 4777 4778 return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope); 4779 }; 4780 4781 self.remove = function(target, events, func, scope) { 4782 if (!target) { 4783 return self; 4784 } 4785 4786 // Old API supported direct ID assignment 4787 if (typeof(target) === "string") { 4788 target = document.getElementById(target); 4789 } 4790 4791 // Old API supported multiple targets 4792 if (target instanceof Array) { 4793 var i = target.length; 4794 4795 while (i--) { 4796 self.remove(target[i], events, func, scope); 4797 } 4798 4799 return self; 4800 } 4801 4802 return self.unbind(target, events instanceof Array ? events.join(' ') : events, func); 4803 }; 4804 4805 self.clear = function(target) { 4806 // Old API supported direct ID assignment 4807 if (typeof(target) === "string") { 4808 target = document.getElementById(target); 4809 } 4810 4811 return self.clean(target); 4812 }; 4813 4814 self.cancel = function(e) { 4815 if (e) { 4816 self.prevent(e); 4817 self.stop(e); 4818 } 4819 4820 return false; 4821 }; 4822 4823 self.prevent = function(e) { 4824 if (!e.preventDefault) { 4825 e = fix(e); 4826 } 4827 4828 e.preventDefault(); 4829 4830 return false; 4831 }; 4832 4833 self.stop = function(e) { 4834 if (!e.stopPropagation) { 4835 e = fix(e); 4836 } 4837 4838 e.stopPropagation(); 4839 4840 return false; 4841 }; 4842 } 4843 4844 namespace.EventUtils = EventUtils; 4845 4846 namespace.Event = new EventUtils(function(id) { 4847 return function(evt) { 4848 tinymce.dom.Event.callNativeHandler(id, evt); 4849 }; 4850 }); 4851 4852 // Bind ready event when tinymce script is loaded 4853 namespace.Event.bind(window, 'ready', function() {}); 4854 4855 namespace = 0; 4856 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando 4857 4858 tinymce.dom.TreeWalker = function(start_node, root_node) { 4859 var node = start_node; 4860 4861 function findSibling(node, start_name, sibling_name, shallow) { 4862 var sibling, parent; 4863 4864 if (node) { 4865 // Walk into nodes if it has a start 4866 if (!shallow && node[start_name]) 4867 return node[start_name]; 4868 4869 // Return the sibling if it has one 4870 if (node != root_node) { 4871 sibling = node[sibling_name]; 4872 if (sibling) 4873 return sibling; 4874 4875 // Walk up the parents to look for siblings 4876 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) { 4877 sibling = parent[sibling_name]; 4878 if (sibling) 4879 return sibling; 4880 } 4881 } 4882 } 4883 }; 4884 4885 this.current = function() { 4886 return node; 4887 }; 4888 4889 this.next = function(shallow) { 4890 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); 4891 }; 4892 4893 this.prev = function(shallow) { 4894 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); 4895 }; 4896 }; 4897 4898 (function(tinymce) { 4899 // Shorten names 4900 var each = tinymce.each, 4901 is = tinymce.is, 4902 isWebKit = tinymce.isWebKit, 4903 isIE = tinymce.isIE, 4904 Entities = tinymce.html.Entities, 4905 simpleSelectorRe = /^([a-z0-9],?)+$/i, 4906 whiteSpaceRegExp = /^[ \t\r\n]*$/; 4907 4908 tinymce.create('tinymce.dom.DOMUtils', { 4909 doc : null, 4910 root : null, 4911 files : null, 4912 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, 4913 props : { 4914 "for" : "htmlFor", 4915 "class" : "className", 4916 className : "className", 4917 checked : "checked", 4918 disabled : "disabled", 4919 maxlength : "maxLength", 4920 readonly : "readOnly", 4921 selected : "selected", 4922 value : "value", 4923 id : "id", 4924 name : "name", 4925 type : "type" 4926 }, 4927 4928 DOMUtils : function(d, s) { 4929 var t = this, globalStyle, name, blockElementsMap; 4930 4931 t.doc = d; 4932 t.win = window; 4933 t.files = {}; 4934 t.cssFlicker = false; 4935 t.counter = 0; 4936 t.stdMode = !tinymce.isIE || d.documentMode >= 8; 4937 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; 4938 t.hasOuterHTML = "outerHTML" in d.createElement("a"); 4939 4940 t.settings = s = tinymce.extend({ 4941 keep_values : false, 4942 hex_colors : 1 4943 }, s); 4944 4945 t.schema = s.schema; 4946 t.styles = new tinymce.html.Styles({ 4947 url_converter : s.url_converter, 4948 url_converter_scope : s.url_converter_scope 4949 }, s.schema); 4950 4951 // Fix IE6SP2 flicker and check it failed for pre SP2 4952 if (tinymce.isIE6) { 4953 try { 4954 d.execCommand('BackgroundImageCache', false, true); 4955 } catch (e) { 4956 t.cssFlicker = true; 4957 } 4958 } 4959 4960 t.fixDoc(d); 4961 t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event; 4962 tinymce.addUnload(t.destroy, t); 4963 blockElementsMap = s.schema ? s.schema.getBlockElements() : {}; 4964 4965 t.isBlock = function(node) { 4966 // This function is called in module pattern style since it might be executed with the wrong this scope 4967 var type = node.nodeType; 4968 4969 // If it's a node then check the type and use the nodeName 4970 if (type) 4971 return !!(type === 1 && blockElementsMap[node.nodeName]); 4972 4973 return !!blockElementsMap[node]; 4974 }; 4975 }, 4976 4977 fixDoc: function(doc) { 4978 var settings = this.settings, name; 4979 4980 if (isIE && settings.schema) { 4981 // Add missing HTML 4/5 elements to IE 4982 ('abbr article aside audio canvas ' + 4983 'details figcaption figure footer ' + 4984 'header hgroup mark menu meter nav ' + 4985 'output progress section summary ' + 4986 'time video').replace(/\w+/g, function(name) { 4987 doc.createElement(name); 4988 }); 4989 4990 // Create all custom elements 4991 for (name in settings.schema.getCustomElements()) { 4992 doc.createElement(name); 4993 } 4994 } 4995 }, 4996 4997 clone: function(node, deep) { 4998 var self = this, clone, doc; 4999 5000 // TODO: Add feature detection here in the future 5001 if (!isIE || node.nodeType !== 1 || deep) { 5002 return node.cloneNode(deep); 5003 } 5004 5005 doc = self.doc; 5006 5007 // Make a HTML5 safe shallow copy 5008 if (!deep) { 5009 clone = doc.createElement(node.nodeName); 5010 5011 // Copy attribs 5012 each(self.getAttribs(node), function(attr) { 5013 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 5014 }); 5015 5016 return clone; 5017 } 5018 /* 5019 // Setup HTML5 patched document fragment 5020 if (!self.frag) { 5021 self.frag = doc.createDocumentFragment(); 5022 self.fixDoc(self.frag); 5023 } 5024 5025 // Make a deep copy by adding it to the document fragment then removing it this removed the :section 5026 clone = doc.createElement('div'); 5027 self.frag.appendChild(clone); 5028 clone.innerHTML = node.outerHTML; 5029 self.frag.removeChild(clone); 5030 */ 5031 return clone.firstChild; 5032 }, 5033 5034 getRoot : function() { 5035 var t = this, s = t.settings; 5036 5037 return (s && t.get(s.root_element)) || t.doc.body; 5038 }, 5039 5040 getViewPort : function(w) { 5041 var d, b; 5042 5043 w = !w ? this.win : w; 5044 d = w.document; 5045 b = this.boxModel ? d.documentElement : d.body; 5046 5047 // Returns viewport size excluding scrollbars 5048 return { 5049 x : w.pageXOffset || b.scrollLeft, 5050 y : w.pageYOffset || b.scrollTop, 5051 w : w.innerWidth || b.clientWidth, 5052 h : w.innerHeight || b.clientHeight 5053 }; 5054 }, 5055 5056 getRect : function(e) { 5057 var p, t = this, sr; 5058 5059 e = t.get(e); 5060 p = t.getPos(e); 5061 sr = t.getSize(e); 5062 5063 return { 5064 x : p.x, 5065 y : p.y, 5066 w : sr.w, 5067 h : sr.h 5068 }; 5069 }, 5070 5071 getSize : function(e) { 5072 var t = this, w, h; 5073 5074 e = t.get(e); 5075 w = t.getStyle(e, 'width'); 5076 h = t.getStyle(e, 'height'); 5077 5078 // Non pixel value, then force offset/clientWidth 5079 if (w.indexOf('px') === -1) 5080 w = 0; 5081 5082 // Non pixel value, then force offset/clientWidth 5083 if (h.indexOf('px') === -1) 5084 h = 0; 5085 5086 return { 5087 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth, 5088 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight 5089 }; 5090 }, 5091 5092 getParent : function(n, f, r) { 5093 return this.getParents(n, f, r, false); 5094 }, 5095 5096 getParents : function(n, f, r, c) { 5097 var t = this, na, se = t.settings, o = []; 5098 5099 n = t.get(n); 5100 c = c === undefined; 5101 5102 if (se.strict_root) 5103 r = r || t.getRoot(); 5104 5105 // Wrap node name as func 5106 if (is(f, 'string')) { 5107 na = f; 5108 5109 if (f === '*') { 5110 f = function(n) {return n.nodeType == 1;}; 5111 } else { 5112 f = function(n) { 5113 return t.is(n, na); 5114 }; 5115 } 5116 } 5117 5118 while (n) { 5119 if (n == r || !n.nodeType || n.nodeType === 9) 5120 break; 5121 5122 if (!f || f(n)) { 5123 if (c) 5124 o.push(n); 5125 else 5126 return n; 5127 } 5128 5129 n = n.parentNode; 5130 } 5131 5132 return c ? o : null; 5133 }, 5134 5135 get : function(e) { 5136 var n; 5137 5138 if (e && this.doc && typeof(e) == 'string') { 5139 n = e; 5140 e = this.doc.getElementById(e); 5141 5142 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 5143 if (e && e.id !== n) 5144 return this.doc.getElementsByName(n)[1]; 5145 } 5146 5147 return e; 5148 }, 5149 5150 getNext : function(node, selector) { 5151 return this._findSib(node, selector, 'nextSibling'); 5152 }, 5153 5154 getPrev : function(node, selector) { 5155 return this._findSib(node, selector, 'previousSibling'); 5156 }, 5157 5158 5159 select : function(pa, s) { 5160 var t = this; 5161 5162 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); 5163 }, 5164 5165 is : function(n, selector) { 5166 var i; 5167 5168 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 5169 if (n.length === undefined) { 5170 // Simple all selector 5171 if (selector === '*') 5172 return n.nodeType == 1; 5173 5174 // Simple selector just elements 5175 if (simpleSelectorRe.test(selector)) { 5176 selector = selector.toLowerCase().split(/,/); 5177 n = n.nodeName.toLowerCase(); 5178 5179 for (i = selector.length - 1; i >= 0; i--) { 5180 if (selector[i] == n) 5181 return true; 5182 } 5183 5184 return false; 5185 } 5186 } 5187 5188 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; 5189 }, 5190 5191 5192 add : function(p, n, a, h, c) { 5193 var t = this; 5194 5195 return this.run(p, function(p) { 5196 var e, k; 5197 5198 e = is(n, 'string') ? t.doc.createElement(n) : n; 5199 t.setAttribs(e, a); 5200 5201 if (h) { 5202 if (h.nodeType) 5203 e.appendChild(h); 5204 else 5205 t.setHTML(e, h); 5206 } 5207 5208 return !c ? p.appendChild(e) : e; 5209 }); 5210 }, 5211 5212 create : function(n, a, h) { 5213 return this.add(this.doc.createElement(n), n, a, h, 1); 5214 }, 5215 5216 createHTML : function(n, a, h) { 5217 var o = '', t = this, k; 5218 5219 o += '<' + n; 5220 5221 for (k in a) { 5222 if (a.hasOwnProperty(k)) 5223 o += ' ' + k + '="' + t.encode(a[k]) + '"'; 5224 } 5225 5226 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 5227 if (typeof(h) != "undefined") 5228 return o + '>' + h + '</' + n + '>'; 5229 5230 return o + ' />'; 5231 }, 5232 5233 remove : function(node, keep_children) { 5234 return this.run(node, function(node) { 5235 var child, parent = node.parentNode; 5236 5237 if (!parent) 5238 return null; 5239 5240 if (keep_children) { 5241 while (child = node.firstChild) { 5242 // IE 8 will crash if you don't remove completely empty text nodes 5243 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) 5244 parent.insertBefore(child, node); 5245 else 5246 node.removeChild(child); 5247 } 5248 } 5249 5250 return parent.removeChild(node); 5251 }); 5252 }, 5253 5254 setStyle : function(n, na, v) { 5255 var t = this; 5256 5257 return t.run(n, function(e) { 5258 var s, i; 5259 5260 s = e.style; 5261 5262 // Camelcase it, if needed 5263 na = na.replace(/-(\D)/g, function(a, b){ 5264 return b.toUpperCase(); 5265 }); 5266 5267 // Default px suffix on these 5268 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) 5269 v += 'px'; 5270 5271 switch (na) { 5272 case 'opacity': 5273 // IE specific opacity 5274 if (isIE) { 5275 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; 5276 5277 if (!n.currentStyle || !n.currentStyle.hasLayout) 5278 s.display = 'inline-block'; 5279 } 5280 5281 // Fix for older browsers 5282 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; 5283 break; 5284 5285 case 'float': 5286 isIE ? s.styleFloat = v : s.cssFloat = v; 5287 break; 5288 5289 default: 5290 s[na] = v || ''; 5291 } 5292 5293 // Force update of the style data 5294 if (t.settings.update_styles) 5295 t.setAttrib(e, 'data-mce-style'); 5296 }); 5297 }, 5298 5299 getStyle : function(n, na, c) { 5300 n = this.get(n); 5301 5302 if (!n) 5303 return; 5304 5305 // Gecko 5306 if (this.doc.defaultView && c) { 5307 // Remove camelcase 5308 na = na.replace(/[A-Z]/g, function(a){ 5309 return '-' + a; 5310 }); 5311 5312 try { 5313 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); 5314 } catch (ex) { 5315 // Old safari might fail 5316 return null; 5317 } 5318 } 5319 5320 // Camelcase it, if needed 5321 na = na.replace(/-(\D)/g, function(a, b){ 5322 return b.toUpperCase(); 5323 }); 5324 5325 if (na == 'float') 5326 na = isIE ? 'styleFloat' : 'cssFloat'; 5327 5328 // IE & Opera 5329 if (n.currentStyle && c) 5330 return n.currentStyle[na]; 5331 5332 return n.style ? n.style[na] : undefined; 5333 }, 5334 5335 setStyles : function(e, o) { 5336 var t = this, s = t.settings, ol; 5337 5338 ol = s.update_styles; 5339 s.update_styles = 0; 5340 5341 each(o, function(v, n) { 5342 t.setStyle(e, n, v); 5343 }); 5344 5345 // Update style info 5346 s.update_styles = ol; 5347 if (s.update_styles) 5348 t.setAttrib(e, s.cssText); 5349 }, 5350 5351 removeAllAttribs: function(e) { 5352 return this.run(e, function(e) { 5353 var i, attrs = e.attributes; 5354 for (i = attrs.length - 1; i >= 0; i--) { 5355 e.removeAttributeNode(attrs.item(i)); 5356 } 5357 }); 5358 }, 5359 5360 setAttrib : function(e, n, v) { 5361 var t = this; 5362 5363 // Whats the point 5364 if (!e || !n) 5365 return; 5366 5367 // Strict XML mode 5368 if (t.settings.strict) 5369 n = n.toLowerCase(); 5370 5371 return this.run(e, function(e) { 5372 var s = t.settings; 5373 var originalValue = e.getAttribute(n); 5374 if (v !== null) { 5375 switch (n) { 5376 case "style": 5377 if (!is(v, 'string')) { 5378 each(v, function(v, n) { 5379 t.setStyle(e, n, v); 5380 }); 5381 5382 return; 5383 } 5384 5385 // No mce_style for elements with these since they might get resized by the user 5386 if (s.keep_values) { 5387 if (v && !t._isRes(v)) 5388 e.setAttribute('data-mce-style', v, 2); 5389 else 5390 e.removeAttribute('data-mce-style', 2); 5391 } 5392 5393 e.style.cssText = v; 5394 break; 5395 5396 case "class": 5397 e.className = v || ''; // Fix IE null bug 5398 break; 5399 5400 case "src": 5401 case "href": 5402 if (s.keep_values) { 5403 if (s.url_converter) 5404 v = s.url_converter.call(s.url_converter_scope || t, v, n, e); 5405 5406 t.setAttrib(e, 'data-mce-' + n, v, 2); 5407 } 5408 5409 break; 5410 5411 case "shape": 5412 e.setAttribute('data-mce-style', v); 5413 break; 5414 } 5415 } 5416 if (is(v) && v !== null && v.length !== 0) 5417 e.setAttribute(n, '' + v, 2); 5418 else 5419 e.removeAttribute(n, 2); 5420 5421 // fire onChangeAttrib event for attributes that have changed 5422 if (tinyMCE.activeEditor && originalValue != v) { 5423 var ed = tinyMCE.activeEditor; 5424 ed.onSetAttrib.dispatch(ed, e, n, v); 5425 } 5426 }); 5427 }, 5428 5429 setAttribs : function(e, o) { 5430 var t = this; 5431 5432 return this.run(e, function(e) { 5433 each(o, function(v, n) { 5434 t.setAttrib(e, n, v); 5435 }); 5436 }); 5437 }, 5438 5439 getAttrib : function(e, n, dv) { 5440 var v, t = this, undef; 5441 5442 e = t.get(e); 5443 5444 if (!e || e.nodeType !== 1) 5445 return dv === undef ? false : dv; 5446 5447 if (!is(dv)) 5448 dv = ''; 5449 5450 // Try the mce variant for these 5451 if (/^(src|href|style|coords|shape)$/.test(n)) { 5452 v = e.getAttribute("data-mce-" + n); 5453 5454 if (v) 5455 return v; 5456 } 5457 5458 if (isIE && t.props[n]) { 5459 v = e[t.props[n]]; 5460 v = v && v.nodeValue ? v.nodeValue : v; 5461 } 5462 5463 if (!v) 5464 v = e.getAttribute(n, 2); 5465 5466 // Check boolean attribs 5467 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { 5468 if (e[t.props[n]] === true && v === '') 5469 return n; 5470 5471 return v ? n : ''; 5472 } 5473 5474 // Inner input elements will override attributes on form elements 5475 if (e.nodeName === "FORM" && e.getAttributeNode(n)) 5476 return e.getAttributeNode(n).nodeValue; 5477 5478 if (n === 'style') { 5479 v = v || e.style.cssText; 5480 5481 if (v) { 5482 v = t.serializeStyle(t.parseStyle(v), e.nodeName); 5483 5484 if (t.settings.keep_values && !t._isRes(v)) 5485 e.setAttribute('data-mce-style', v); 5486 } 5487 } 5488 5489 // Remove Apple and WebKit stuff 5490 if (isWebKit && n === "class" && v) 5491 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); 5492 5493 // Handle IE issues 5494 if (isIE) { 5495 switch (n) { 5496 case 'rowspan': 5497 case 'colspan': 5498 // IE returns 1 as default value 5499 if (v === 1) 5500 v = ''; 5501 5502 break; 5503 5504 case 'size': 5505 // IE returns +0 as default value for size 5506 if (v === '+0' || v === 20 || v === 0) 5507 v = ''; 5508 5509 break; 5510 5511 case 'width': 5512 case 'height': 5513 case 'vspace': 5514 case 'checked': 5515 case 'disabled': 5516 case 'readonly': 5517 if (v === 0) 5518 v = ''; 5519 5520 break; 5521 5522 case 'hspace': 5523 // IE returns -1 as default value 5524 if (v === -1) 5525 v = ''; 5526 5527 break; 5528 5529 case 'maxlength': 5530 case 'tabindex': 5531 // IE returns default value 5532 if (v === 32768 || v === 2147483647 || v === '32768') 5533 v = ''; 5534 5535 break; 5536 5537 case 'multiple': 5538 case 'compact': 5539 case 'noshade': 5540 case 'nowrap': 5541 if (v === 65535) 5542 return n; 5543 5544 return dv; 5545 5546 case 'shape': 5547 v = v.toLowerCase(); 5548 break; 5549 5550 default: 5551 // IE has odd anonymous function for event attributes 5552 if (n.indexOf('on') === 0 && v) 5553 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); 5554 } 5555 } 5556 5557 return (v !== undef && v !== null && v !== '') ? '' + v : dv; 5558 }, 5559 5560 getPos : function(n, ro) { 5561 var t = this, x = 0, y = 0, e, d = t.doc, r; 5562 5563 n = t.get(n); 5564 ro = ro || d.body; 5565 5566 if (n) { 5567 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 5568 if (n.getBoundingClientRect) { 5569 n = n.getBoundingClientRect(); 5570 e = t.boxModel ? d.documentElement : d.body; 5571 5572 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 5573 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 5574 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; 5575 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; 5576 5577 return {x : x, y : y}; 5578 } 5579 5580 r = n; 5581 while (r && r != ro && r.nodeType) { 5582 x += r.offsetLeft || 0; 5583 y += r.offsetTop || 0; 5584 r = r.offsetParent; 5585 } 5586 5587 r = n.parentNode; 5588 while (r && r != ro && r.nodeType) { 5589 x -= r.scrollLeft || 0; 5590 y -= r.scrollTop || 0; 5591 r = r.parentNode; 5592 } 5593 } 5594 5595 return {x : x, y : y}; 5596 }, 5597 5598 parseStyle : function(st) { 5599 return this.styles.parse(st); 5600 }, 5601 5602 serializeStyle : function(o, name) { 5603 return this.styles.serialize(o, name); 5604 }, 5605 5606 addStyle: function(cssText) { 5607 var doc = this.doc, head; 5608 5609 // Create style element if needed 5610 styleElm = doc.getElementById('mceDefaultStyles'); 5611 if (!styleElm) { 5612 styleElm = doc.createElement('style'), 5613 styleElm.id = 'mceDefaultStyles'; 5614 styleElm.type = 'text/css'; 5615 5616 head = doc.getElementsByTagName('head')[0] 5617 if (head.firstChild) { 5618 head.insertBefore(styleElm, head.firstChild); 5619 } else { 5620 head.appendChild(styleElm); 5621 } 5622 } 5623 5624 // Append style data to old or new style element 5625 if (styleElm.styleSheet) { 5626 styleElm.styleSheet.cssText += cssText; 5627 } else { 5628 styleElm.appendChild(doc.createTextNode(cssText)); 5629 } 5630 }, 5631 5632 loadCSS : function(u) { 5633 var t = this, d = t.doc, head; 5634 5635 if (!u) 5636 u = ''; 5637 5638 head = d.getElementsByTagName('head')[0]; 5639 5640 each(u.split(','), function(u) { 5641 var link; 5642 5643 if (t.files[u]) 5644 return; 5645 5646 t.files[u] = true; 5647 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); 5648 5649 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 5650 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading 5651 // It's ugly but it seems to work fine. 5652 if (isIE && d.documentMode && d.recalc) { 5653 link.onload = function() { 5654 if (d.recalc) 5655 d.recalc(); 5656 5657 link.onload = null; 5658 }; 5659 } 5660 5661 head.appendChild(link); 5662 }); 5663 }, 5664 5665 addClass : function(e, c) { 5666 return this.run(e, function(e) { 5667 var o; 5668 5669 if (!c) 5670 return 0; 5671 5672 if (this.hasClass(e, c)) 5673 return e.className; 5674 5675 o = this.removeClass(e, c); 5676 5677 return e.className = (o != '' ? (o + ' ') : '') + c; 5678 }); 5679 }, 5680 5681 removeClass : function(e, c) { 5682 var t = this, re; 5683 5684 return t.run(e, function(e) { 5685 var v; 5686 5687 if (t.hasClass(e, c)) { 5688 if (!re) 5689 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); 5690 5691 v = e.className.replace(re, ' '); 5692 v = tinymce.trim(v != ' ' ? v : ''); 5693 5694 e.className = v; 5695 5696 // Empty class attr 5697 if (!v) { 5698 e.removeAttribute('class'); 5699 e.removeAttribute('className'); 5700 } 5701 5702 return v; 5703 } 5704 5705 return e.className; 5706 }); 5707 }, 5708 5709 hasClass : function(n, c) { 5710 n = this.get(n); 5711 5712 if (!n || !c) 5713 return false; 5714 5715 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; 5716 }, 5717 5718 show : function(e) { 5719 return this.setStyle(e, 'display', 'block'); 5720 }, 5721 5722 hide : function(e) { 5723 return this.setStyle(e, 'display', 'none'); 5724 }, 5725 5726 isHidden : function(e) { 5727 e = this.get(e); 5728 5729 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; 5730 }, 5731 5732 uniqueId : function(p) { 5733 return (!p ? 'mce_' : p) + (this.counter++); 5734 }, 5735 5736 setHTML : function(element, html) { 5737 var self = this; 5738 5739 return self.run(element, function(element) { 5740 if (isIE) { 5741 // Remove all child nodes, IE keeps empty text nodes in DOM 5742 while (element.firstChild) 5743 element.removeChild(element.firstChild); 5744 5745 try { 5746 // IE will remove comments from the beginning 5747 // unless you padd the contents with something 5748 element.innerHTML = '<br />' + html; 5749 element.removeChild(element.firstChild); 5750 } catch (ex) { 5751 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p 5752 // This seems to fix this problem 5753 5754 // Create new div with HTML contents and a BR infront to keep comments 5755 var newElement = self.create('div'); 5756 newElement.innerHTML = '<br />' + html; 5757 5758 // Add all children from div to target 5759 each (tinymce.grep(newElement.childNodes), function(node, i) { 5760 // Skip br element 5761 if (i && element.canHaveHTML) 5762 element.appendChild(node); 5763 }); 5764 } 5765 } else 5766 element.innerHTML = html; 5767 5768 return html; 5769 }); 5770 }, 5771 5772 getOuterHTML : function(elm) { 5773 var doc, self = this; 5774 5775 elm = self.get(elm); 5776 5777 if (!elm) 5778 return null; 5779 5780 if (elm.nodeType === 1 && self.hasOuterHTML) 5781 return elm.outerHTML; 5782 5783 doc = (elm.ownerDocument || self.doc).createElement("body"); 5784 doc.appendChild(elm.cloneNode(true)); 5785 5786 return doc.innerHTML; 5787 }, 5788 5789 setOuterHTML : function(e, h, d) { 5790 var t = this; 5791 5792 function setHTML(e, h, d) { 5793 var n, tp; 5794 5795 tp = d.createElement("body"); 5796 tp.innerHTML = h; 5797 5798 n = tp.lastChild; 5799 while (n) { 5800 t.insertAfter(n.cloneNode(true), e); 5801 n = n.previousSibling; 5802 } 5803 5804 t.remove(e); 5805 }; 5806 5807 return this.run(e, function(e) { 5808 e = t.get(e); 5809 5810 // Only set HTML on elements 5811 if (e.nodeType == 1) { 5812 d = d || e.ownerDocument || t.doc; 5813 5814 if (isIE) { 5815 try { 5816 // Try outerHTML for IE it sometimes produces an unknown runtime error 5817 if (isIE && e.nodeType == 1) 5818 e.outerHTML = h; 5819 else 5820 setHTML(e, h, d); 5821 } catch (ex) { 5822 // Fix for unknown runtime error 5823 setHTML(e, h, d); 5824 } 5825 } else 5826 setHTML(e, h, d); 5827 } 5828 }); 5829 }, 5830 5831 decode : Entities.decode, 5832 5833 encode : Entities.encodeAllRaw, 5834 5835 insertAfter : function(node, reference_node) { 5836 reference_node = this.get(reference_node); 5837 5838 return this.run(node, function(node) { 5839 var parent, nextSibling; 5840 5841 parent = reference_node.parentNode; 5842 nextSibling = reference_node.nextSibling; 5843 5844 if (nextSibling) 5845 parent.insertBefore(node, nextSibling); 5846 else 5847 parent.appendChild(node); 5848 5849 return node; 5850 }); 5851 }, 5852 5853 replace : function(n, o, k) { 5854 var t = this; 5855 5856 if (is(o, 'array')) 5857 n = n.cloneNode(true); 5858 5859 return t.run(o, function(o) { 5860 if (k) { 5861 each(tinymce.grep(o.childNodes), function(c) { 5862 n.appendChild(c); 5863 }); 5864 } 5865 5866 return o.parentNode.replaceChild(n, o); 5867 }); 5868 }, 5869 5870 rename : function(elm, name) { 5871 var t = this, newElm; 5872 5873 if (elm.nodeName != name.toUpperCase()) { 5874 // Rename block element 5875 newElm = t.create(name); 5876 5877 // Copy attribs to new block 5878 each(t.getAttribs(elm), function(attr_node) { 5879 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); 5880 }); 5881 5882 // Replace block 5883 t.replace(newElm, elm, 1); 5884 } 5885 5886 return newElm || elm; 5887 }, 5888 5889 findCommonAncestor : function(a, b) { 5890 var ps = a, pe; 5891 5892 while (ps) { 5893 pe = b; 5894 5895 while (pe && ps != pe) 5896 pe = pe.parentNode; 5897 5898 if (ps == pe) 5899 break; 5900 5901 ps = ps.parentNode; 5902 } 5903 5904 if (!ps && a.ownerDocument) 5905 return a.ownerDocument.documentElement; 5906 5907 return ps; 5908 }, 5909 5910 toHex : function(s) { 5911 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); 5912 5913 function hex(s) { 5914 s = parseInt(s, 10).toString(16); 5915 5916 return s.length > 1 ? s : '0' + s; // 0 -> 00 5917 }; 5918 5919 if (c) { 5920 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); 5921 5922 return s; 5923 } 5924 5925 return s; 5926 }, 5927 5928 getClasses : function() { 5929 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; 5930 5931 if (t.classes) 5932 return t.classes; 5933 5934 function addClasses(s) { 5935 // IE style imports 5936 each(s.imports, function(r) { 5937 addClasses(r); 5938 }); 5939 5940 each(s.cssRules || s.rules, function(r) { 5941 // Real type or fake it on IE 5942 switch (r.type || 1) { 5943 // Rule 5944 case 1: 5945 if (r.selectorText) { 5946 each(r.selectorText.split(','), function(v) { 5947 v = v.replace(/^\s*|\s*$|^\s\./g, ""); 5948 5949 // Is internal or it doesn't contain a class 5950 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) 5951 return; 5952 5953 // Remove everything but class name 5954 ov = v; 5955 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); 5956 5957 // Filter classes 5958 if (f && !(v = f(v, ov))) 5959 return; 5960 5961 if (!lo[v]) { 5962 cl.push({'class' : v}); 5963 lo[v] = 1; 5964 } 5965 }); 5966 } 5967 break; 5968 5969 // Import 5970 case 3: 5971 addClasses(r.styleSheet); 5972 break; 5973 } 5974 }); 5975 }; 5976 5977 try { 5978 each(t.doc.styleSheets, addClasses); 5979 } catch (ex) { 5980 // Ignore 5981 } 5982 5983 if (cl.length > 0) 5984 t.classes = cl; 5985 5986 return cl; 5987 }, 5988 5989 run : function(e, f, s) { 5990 var t = this, o; 5991 5992 if (t.doc && typeof(e) === 'string') 5993 e = t.get(e); 5994 5995 if (!e) 5996 return false; 5997 5998 s = s || this; 5999 if (!e.nodeType && (e.length || e.length === 0)) { 6000 o = []; 6001 6002 each(e, function(e, i) { 6003 if (e) { 6004 if (typeof(e) == 'string') 6005 e = t.doc.getElementById(e); 6006 6007 o.push(f.call(s, e, i)); 6008 } 6009 }); 6010 6011 return o; 6012 } 6013 6014 return f.call(s, e); 6015 }, 6016 6017 getAttribs : function(n) { 6018 var o; 6019 6020 n = this.get(n); 6021 6022 if (!n) 6023 return []; 6024 6025 if (isIE) { 6026 o = []; 6027 6028 // Object will throw exception in IE 6029 if (n.nodeName == 'OBJECT') 6030 return n.attributes; 6031 6032 // IE doesn't keep the selected attribute if you clone option elements 6033 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) 6034 o.push({specified : 1, nodeName : 'selected'}); 6035 6036 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 6037 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { 6038 o.push({specified : 1, nodeName : a}); 6039 }); 6040 6041 return o; 6042 } 6043 6044 return n.attributes; 6045 }, 6046 6047 isEmpty : function(node, elements) { 6048 var self = this, i, attributes, type, walker, name, brCount = 0; 6049 6050 node = node.firstChild; 6051 if (node) { 6052 walker = new tinymce.dom.TreeWalker(node, node.parentNode); 6053 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; 6054 6055 do { 6056 type = node.nodeType; 6057 6058 if (type === 1) { 6059 // Ignore bogus elements 6060 if (node.getAttribute('data-mce-bogus')) 6061 continue; 6062 6063 // Keep empty elements like <img /> 6064 name = node.nodeName.toLowerCase(); 6065 if (elements && elements[name]) { 6066 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 6067 if (name === 'br') { 6068 brCount++; 6069 continue; 6070 } 6071 6072 return false; 6073 } 6074 6075 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 6076 attributes = self.getAttribs(node); 6077 i = node.attributes.length; 6078 while (i--) { 6079 name = node.attributes[i].nodeName; 6080 if (name === "name" || name === 'data-mce-bookmark') 6081 return false; 6082 } 6083 } 6084 6085 // Keep comment nodes 6086 if (type == 8) 6087 return false; 6088 6089 // Keep non whitespace text nodes 6090 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) 6091 return false; 6092 } while (node = walker.next()); 6093 } 6094 6095 return brCount <= 1; 6096 }, 6097 6098 destroy : function(s) { 6099 var t = this; 6100 6101 t.win = t.doc = t.root = t.events = t.frag = null; 6102 6103 // Manual destroy then remove unload handler 6104 if (!s) 6105 tinymce.removeUnload(t.destroy); 6106 }, 6107 6108 createRng : function() { 6109 var d = this.doc; 6110 6111 return d.createRange ? d.createRange() : new tinymce.dom.Range(this); 6112 }, 6113 6114 nodeIndex : function(node, normalized) { 6115 var idx = 0, lastNodeType, lastNode, nodeType; 6116 6117 if (node) { 6118 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { 6119 nodeType = node.nodeType; 6120 6121 // Normalize text nodes 6122 if (normalized && nodeType == 3) { 6123 if (nodeType == lastNodeType || !node.nodeValue.length) 6124 continue; 6125 } 6126 idx++; 6127 lastNodeType = nodeType; 6128 } 6129 } 6130 6131 return idx; 6132 }, 6133 6134 split : function(pe, e, re) { 6135 var t = this, r = t.createRng(), bef, aft, pa; 6136 6137 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense 6138 // but we don't want that in our code since it serves no purpose for the end user 6139 // For example if this is chopped: 6140 // <p>text 1<span><b>CHOP</b></span>text 2</p> 6141 // would produce: 6142 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 6143 // this function will then trim of empty edges and produce: 6144 // <p>text 1</p><b>CHOP</b><p>text 2</p> 6145 function trim(node) { 6146 var i, children = node.childNodes, type = node.nodeType; 6147 6148 function surroundedBySpans(node) { 6149 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 6150 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 6151 return previousIsSpan && nextIsSpan; 6152 } 6153 6154 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') 6155 return; 6156 6157 for (i = children.length - 1; i >= 0; i--) 6158 trim(children[i]); 6159 6160 if (type != 9) { 6161 // Keep non whitespace text nodes 6162 if (type == 3 && node.nodeValue.length > 0) { 6163 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 6164 // Also keep text nodes with only spaces if surrounded by spans. 6165 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 6166 var trimmedLength = tinymce.trim(node.nodeValue).length; 6167 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) 6168 return; 6169 } else if (type == 1) { 6170 // If the only child is a bookmark then move it up 6171 children = node.childNodes; 6172 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') 6173 node.parentNode.insertBefore(children[0], node); 6174 6175 // Keep non empty elements or img, hr etc 6176 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) 6177 return; 6178 } 6179 6180 t.remove(node); 6181 } 6182 6183 return node; 6184 }; 6185 6186 if (pe && e) { 6187 // Get before chunk 6188 r.setStart(pe.parentNode, t.nodeIndex(pe)); 6189 r.setEnd(e.parentNode, t.nodeIndex(e)); 6190 bef = r.extractContents(); 6191 6192 // Get after chunk 6193 r = t.createRng(); 6194 r.setStart(e.parentNode, t.nodeIndex(e) + 1); 6195 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); 6196 aft = r.extractContents(); 6197 6198 // Insert before chunk 6199 pa = pe.parentNode; 6200 pa.insertBefore(trim(bef), pe); 6201 6202 // Insert middle chunk 6203 if (re) 6204 pa.replaceChild(re, e); 6205 else 6206 pa.insertBefore(e, pe); 6207 6208 // Insert after chunk 6209 pa.insertBefore(trim(aft), pe); 6210 t.remove(pe); 6211 6212 return re || e; 6213 } 6214 }, 6215 6216 bind : function(target, name, func, scope) { 6217 return this.events.add(target, name, func, scope || this); 6218 }, 6219 6220 unbind : function(target, name, func) { 6221 return this.events.remove(target, name, func); 6222 }, 6223 6224 fire : function(target, name, evt) { 6225 return this.events.fire(target, name, evt); 6226 }, 6227 6228 // Returns the content editable state of a node 6229 getContentEditable: function(node) { 6230 var contentEditable; 6231 6232 // Check type 6233 if (node.nodeType != 1) { 6234 return null; 6235 } 6236 6237 // Check for fake content editable 6238 contentEditable = node.getAttribute("data-mce-contenteditable"); 6239 if (contentEditable && contentEditable !== "inherit") { 6240 return contentEditable; 6241 } 6242 6243 // Check for real content editable 6244 return node.contentEditable !== "inherit" ? node.contentEditable : null; 6245 }, 6246 6247 6248 _findSib : function(node, selector, name) { 6249 var t = this, f = selector; 6250 6251 if (node) { 6252 // If expression make a function of it using is 6253 if (is(f, 'string')) { 6254 f = function(node) { 6255 return t.is(node, selector); 6256 }; 6257 } 6258 6259 // Loop all siblings 6260 for (node = node[name]; node; node = node[name]) { 6261 if (f(node)) 6262 return node; 6263 } 6264 } 6265 6266 return null; 6267 }, 6268 6269 _isRes : function(c) { 6270 // Is live resizble element 6271 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c); 6272 } 6273 6274 /* 6275 walk : function(n, f, s) { 6276 var d = this.doc, w; 6277 6278 if (d.createTreeWalker) { 6279 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); 6280 6281 while ((n = w.nextNode()) != null) 6282 f.call(s || this, n); 6283 } else 6284 tinymce.walk(n, f, 'childNodes', s); 6285 } 6286 */ 6287 6288 /* 6289 toRGB : function(s) { 6290 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); 6291 6292 if (c) { 6293 // #FFF -> #FFFFFF 6294 if (!is(c[3])) 6295 c[3] = c[2] = c[1]; 6296 6297 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; 6298 } 6299 6300 return s; 6301 } 6302 */ 6303 }); 6304 6305 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); 6306 })(tinymce); 6307 6308 (function(ns) { 6309 // Range constructor 6310 function Range(dom) { 6311 var t = this, 6312 doc = dom.doc, 6313 EXTRACT = 0, 6314 CLONE = 1, 6315 DELETE = 2, 6316 TRUE = true, 6317 FALSE = false, 6318 START_OFFSET = 'startOffset', 6319 START_CONTAINER = 'startContainer', 6320 END_CONTAINER = 'endContainer', 6321 END_OFFSET = 'endOffset', 6322 extend = tinymce.extend, 6323 nodeIndex = dom.nodeIndex; 6324 6325 extend(t, { 6326 // Inital states 6327 startContainer : doc, 6328 startOffset : 0, 6329 endContainer : doc, 6330 endOffset : 0, 6331 collapsed : TRUE, 6332 commonAncestorContainer : doc, 6333 6334 // Range constants 6335 START_TO_START : 0, 6336 START_TO_END : 1, 6337 END_TO_END : 2, 6338 END_TO_START : 3, 6339 6340 // Public methods 6341 setStart : setStart, 6342 setEnd : setEnd, 6343 setStartBefore : setStartBefore, 6344 setStartAfter : setStartAfter, 6345 setEndBefore : setEndBefore, 6346 setEndAfter : setEndAfter, 6347 collapse : collapse, 6348 selectNode : selectNode, 6349 selectNodeContents : selectNodeContents, 6350 compareBoundaryPoints : compareBoundaryPoints, 6351 deleteContents : deleteContents, 6352 extractContents : extractContents, 6353 cloneContents : cloneContents, 6354 insertNode : insertNode, 6355 surroundContents : surroundContents, 6356 cloneRange : cloneRange, 6357 toStringIE : toStringIE 6358 }); 6359 6360 function createDocumentFragment() { 6361 return doc.createDocumentFragment(); 6362 }; 6363 6364 function setStart(n, o) { 6365 _setEndPoint(TRUE, n, o); 6366 }; 6367 6368 function setEnd(n, o) { 6369 _setEndPoint(FALSE, n, o); 6370 }; 6371 6372 function setStartBefore(n) { 6373 setStart(n.parentNode, nodeIndex(n)); 6374 }; 6375 6376 function setStartAfter(n) { 6377 setStart(n.parentNode, nodeIndex(n) + 1); 6378 }; 6379 6380 function setEndBefore(n) { 6381 setEnd(n.parentNode, nodeIndex(n)); 6382 }; 6383 6384 function setEndAfter(n) { 6385 setEnd(n.parentNode, nodeIndex(n) + 1); 6386 }; 6387 6388 function collapse(ts) { 6389 if (ts) { 6390 t[END_CONTAINER] = t[START_CONTAINER]; 6391 t[END_OFFSET] = t[START_OFFSET]; 6392 } else { 6393 t[START_CONTAINER] = t[END_CONTAINER]; 6394 t[START_OFFSET] = t[END_OFFSET]; 6395 } 6396 6397 t.collapsed = TRUE; 6398 }; 6399 6400 function selectNode(n) { 6401 setStartBefore(n); 6402 setEndAfter(n); 6403 }; 6404 6405 function selectNodeContents(n) { 6406 setStart(n, 0); 6407 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 6408 }; 6409 6410 function compareBoundaryPoints(h, r) { 6411 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], 6412 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 6413 6414 // Check START_TO_START 6415 if (h === 0) 6416 return _compareBoundaryPoints(sc, so, rsc, rso); 6417 6418 // Check START_TO_END 6419 if (h === 1) 6420 return _compareBoundaryPoints(ec, eo, rsc, rso); 6421 6422 // Check END_TO_END 6423 if (h === 2) 6424 return _compareBoundaryPoints(ec, eo, rec, reo); 6425 6426 // Check END_TO_START 6427 if (h === 3) 6428 return _compareBoundaryPoints(sc, so, rec, reo); 6429 }; 6430 6431 function deleteContents() { 6432 _traverse(DELETE); 6433 }; 6434 6435 function extractContents() { 6436 return _traverse(EXTRACT); 6437 }; 6438 6439 function cloneContents() { 6440 return _traverse(CLONE); 6441 }; 6442 6443 function insertNode(n) { 6444 var startContainer = this[START_CONTAINER], 6445 startOffset = this[START_OFFSET], nn, o; 6446 6447 // Node is TEXT_NODE or CDATA 6448 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 6449 if (!startOffset) { 6450 // At the start of text 6451 startContainer.parentNode.insertBefore(n, startContainer); 6452 } else if (startOffset >= startContainer.nodeValue.length) { 6453 // At the end of text 6454 dom.insertAfter(n, startContainer); 6455 } else { 6456 // Middle, need to split 6457 nn = startContainer.splitText(startOffset); 6458 startContainer.parentNode.insertBefore(n, nn); 6459 } 6460 } else { 6461 // Insert element node 6462 if (startContainer.childNodes.length > 0) 6463 o = startContainer.childNodes[startOffset]; 6464 6465 if (o) 6466 startContainer.insertBefore(n, o); 6467 else 6468 startContainer.appendChild(n); 6469 } 6470 }; 6471 6472 function surroundContents(n) { 6473 var f = t.extractContents(); 6474 6475 t.insertNode(n); 6476 n.appendChild(f); 6477 t.selectNode(n); 6478 }; 6479 6480 function cloneRange() { 6481 return extend(new Range(dom), { 6482 startContainer : t[START_CONTAINER], 6483 startOffset : t[START_OFFSET], 6484 endContainer : t[END_CONTAINER], 6485 endOffset : t[END_OFFSET], 6486 collapsed : t.collapsed, 6487 commonAncestorContainer : t.commonAncestorContainer 6488 }); 6489 }; 6490 6491 // Private methods 6492 6493 function _getSelectedNode(container, offset) { 6494 var child; 6495 6496 if (container.nodeType == 3 /* TEXT_NODE */) 6497 return container; 6498 6499 if (offset < 0) 6500 return container; 6501 6502 child = container.firstChild; 6503 while (child && offset > 0) { 6504 --offset; 6505 child = child.nextSibling; 6506 } 6507 6508 if (child) 6509 return child; 6510 6511 return container; 6512 }; 6513 6514 function _isCollapsed() { 6515 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); 6516 }; 6517 6518 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { 6519 var c, offsetC, n, cmnRoot, childA, childB; 6520 6521 // In the first case the boundary-points have the same container. A is before B 6522 // if its offset is less than the offset of B, A is equal to B if its offset is 6523 // equal to the offset of B, and A is after B if its offset is greater than the 6524 // offset of B. 6525 if (containerA == containerB) { 6526 if (offsetA == offsetB) 6527 return 0; // equal 6528 6529 if (offsetA < offsetB) 6530 return -1; // before 6531 6532 return 1; // after 6533 } 6534 6535 // In the second case a child node C of the container of A is an ancestor 6536 // container of B. In this case, A is before B if the offset of A is less than or 6537 // equal to the index of the child node C and A is after B otherwise. 6538 c = containerB; 6539 while (c && c.parentNode != containerA) 6540 c = c.parentNode; 6541 6542 if (c) { 6543 offsetC = 0; 6544 n = containerA.firstChild; 6545 6546 while (n != c && offsetC < offsetA) { 6547 offsetC++; 6548 n = n.nextSibling; 6549 } 6550 6551 if (offsetA <= offsetC) 6552 return -1; // before 6553 6554 return 1; // after 6555 } 6556 6557 // In the third case a child node C of the container of B is an ancestor container 6558 // of A. In this case, A is before B if the index of the child node C is less than 6559 // the offset of B and A is after B otherwise. 6560 c = containerA; 6561 while (c && c.parentNode != containerB) { 6562 c = c.parentNode; 6563 } 6564 6565 if (c) { 6566 offsetC = 0; 6567 n = containerB.firstChild; 6568 6569 while (n != c && offsetC < offsetB) { 6570 offsetC++; 6571 n = n.nextSibling; 6572 } 6573 6574 if (offsetC < offsetB) 6575 return -1; // before 6576 6577 return 1; // after 6578 } 6579 6580 // In the fourth case, none of three other cases hold: the containers of A and B 6581 // are siblings or descendants of sibling nodes. In this case, A is before B if 6582 // the container of A is before the container of B in a pre-order traversal of the 6583 // Ranges' context tree and A is after B otherwise. 6584 cmnRoot = dom.findCommonAncestor(containerA, containerB); 6585 childA = containerA; 6586 6587 while (childA && childA.parentNode != cmnRoot) 6588 childA = childA.parentNode; 6589 6590 if (!childA) 6591 childA = cmnRoot; 6592 6593 childB = containerB; 6594 while (childB && childB.parentNode != cmnRoot) 6595 childB = childB.parentNode; 6596 6597 if (!childB) 6598 childB = cmnRoot; 6599 6600 if (childA == childB) 6601 return 0; // equal 6602 6603 n = cmnRoot.firstChild; 6604 while (n) { 6605 if (n == childA) 6606 return -1; // before 6607 6608 if (n == childB) 6609 return 1; // after 6610 6611 n = n.nextSibling; 6612 } 6613 }; 6614 6615 function _setEndPoint(st, n, o) { 6616 var ec, sc; 6617 6618 if (st) { 6619 t[START_CONTAINER] = n; 6620 t[START_OFFSET] = o; 6621 } else { 6622 t[END_CONTAINER] = n; 6623 t[END_OFFSET] = o; 6624 } 6625 6626 // If one boundary-point of a Range is set to have a root container 6627 // other than the current one for the Range, the Range is collapsed to 6628 // the new position. This enforces the restriction that both boundary- 6629 // points of a Range must have the same root container. 6630 ec = t[END_CONTAINER]; 6631 while (ec.parentNode) 6632 ec = ec.parentNode; 6633 6634 sc = t[START_CONTAINER]; 6635 while (sc.parentNode) 6636 sc = sc.parentNode; 6637 6638 if (sc == ec) { 6639 // The start position of a Range is guaranteed to never be after the 6640 // end position. To enforce this restriction, if the start is set to 6641 // be at a position after the end, the Range is collapsed to that 6642 // position. 6643 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) 6644 t.collapse(st); 6645 } else 6646 t.collapse(st); 6647 6648 t.collapsed = _isCollapsed(); 6649 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); 6650 }; 6651 6652 function _traverse(how) { 6653 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 6654 6655 if (t[START_CONTAINER] == t[END_CONTAINER]) 6656 return _traverseSameContainer(how); 6657 6658 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6659 if (p == t[START_CONTAINER]) 6660 return _traverseCommonStartContainer(c, how); 6661 6662 ++endContainerDepth; 6663 } 6664 6665 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 6666 if (p == t[END_CONTAINER]) 6667 return _traverseCommonEndContainer(c, how); 6668 6669 ++startContainerDepth; 6670 } 6671 6672 depthDiff = startContainerDepth - endContainerDepth; 6673 6674 startNode = t[START_CONTAINER]; 6675 while (depthDiff > 0) { 6676 startNode = startNode.parentNode; 6677 depthDiff--; 6678 } 6679 6680 endNode = t[END_CONTAINER]; 6681 while (depthDiff < 0) { 6682 endNode = endNode.parentNode; 6683 depthDiff++; 6684 } 6685 6686 // ascend the ancestor hierarchy until we have a common parent. 6687 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 6688 startNode = sp; 6689 endNode = ep; 6690 } 6691 6692 return _traverseCommonAncestors(startNode, endNode, how); 6693 }; 6694 6695 function _traverseSameContainer(how) { 6696 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 6697 6698 if (how != DELETE) 6699 frag = createDocumentFragment(); 6700 6701 // If selection is empty, just return the fragment 6702 if (t[START_OFFSET] == t[END_OFFSET]) 6703 return frag; 6704 6705 // Text node needs special case handling 6706 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { 6707 // get the substring 6708 s = t[START_CONTAINER].nodeValue; 6709 sub = s.substring(t[START_OFFSET], t[END_OFFSET]); 6710 6711 // set the original text node to its new value 6712 if (how != CLONE) { 6713 n = t[START_CONTAINER]; 6714 start = t[START_OFFSET]; 6715 len = t[END_OFFSET] - t[START_OFFSET]; 6716 6717 if (start === 0 && len >= n.nodeValue.length - 1) { 6718 n.parentNode.removeChild(n); 6719 } else { 6720 n.deleteData(start, len); 6721 } 6722 6723 // Nothing is partially selected, so collapse to start point 6724 t.collapse(TRUE); 6725 } 6726 6727 if (how == DELETE) 6728 return; 6729 6730 if (sub.length > 0) { 6731 frag.appendChild(doc.createTextNode(sub)); 6732 } 6733 6734 return frag; 6735 } 6736 6737 // Copy nodes between the start/end offsets. 6738 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); 6739 cnt = t[END_OFFSET] - t[START_OFFSET]; 6740 6741 while (n && cnt > 0) { 6742 sibling = n.nextSibling; 6743 xferNode = _traverseFullySelected(n, how); 6744 6745 if (frag) 6746 frag.appendChild( xferNode ); 6747 6748 --cnt; 6749 n = sibling; 6750 } 6751 6752 // Nothing is partially selected, so collapse to start point 6753 if (how != CLONE) 6754 t.collapse(TRUE); 6755 6756 return frag; 6757 }; 6758 6759 function _traverseCommonStartContainer(endAncestor, how) { 6760 var frag, n, endIdx, cnt, sibling, xferNode; 6761 6762 if (how != DELETE) 6763 frag = createDocumentFragment(); 6764 6765 n = _traverseRightBoundary(endAncestor, how); 6766 6767 if (frag) 6768 frag.appendChild(n); 6769 6770 endIdx = nodeIndex(endAncestor); 6771 cnt = endIdx - t[START_OFFSET]; 6772 6773 if (cnt <= 0) { 6774 // Collapse to just before the endAncestor, which 6775 // is partially selected. 6776 if (how != CLONE) { 6777 t.setEndBefore(endAncestor); 6778 t.collapse(FALSE); 6779 } 6780 6781 return frag; 6782 } 6783 6784 n = endAncestor.previousSibling; 6785 while (cnt > 0) { 6786 sibling = n.previousSibling; 6787 xferNode = _traverseFullySelected(n, how); 6788 6789 if (frag) 6790 frag.insertBefore(xferNode, frag.firstChild); 6791 6792 --cnt; 6793 n = sibling; 6794 } 6795 6796 // Collapse to just before the endAncestor, which 6797 // is partially selected. 6798 if (how != CLONE) { 6799 t.setEndBefore(endAncestor); 6800 t.collapse(FALSE); 6801 } 6802 6803 return frag; 6804 }; 6805 6806 function _traverseCommonEndContainer(startAncestor, how) { 6807 var frag, startIdx, n, cnt, sibling, xferNode; 6808 6809 if (how != DELETE) 6810 frag = createDocumentFragment(); 6811 6812 n = _traverseLeftBoundary(startAncestor, how); 6813 if (frag) 6814 frag.appendChild(n); 6815 6816 startIdx = nodeIndex(startAncestor); 6817 ++startIdx; // Because we already traversed it 6818 6819 cnt = t[END_OFFSET] - startIdx; 6820 n = startAncestor.nextSibling; 6821 while (n && cnt > 0) { 6822 sibling = n.nextSibling; 6823 xferNode = _traverseFullySelected(n, how); 6824 6825 if (frag) 6826 frag.appendChild(xferNode); 6827 6828 --cnt; 6829 n = sibling; 6830 } 6831 6832 if (how != CLONE) { 6833 t.setStartAfter(startAncestor); 6834 t.collapse(TRUE); 6835 } 6836 6837 return frag; 6838 }; 6839 6840 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 6841 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; 6842 6843 if (how != DELETE) 6844 frag = createDocumentFragment(); 6845 6846 n = _traverseLeftBoundary(startAncestor, how); 6847 if (frag) 6848 frag.appendChild(n); 6849 6850 commonParent = startAncestor.parentNode; 6851 startOffset = nodeIndex(startAncestor); 6852 endOffset = nodeIndex(endAncestor); 6853 ++startOffset; 6854 6855 cnt = endOffset - startOffset; 6856 sibling = startAncestor.nextSibling; 6857 6858 while (cnt > 0) { 6859 nextSibling = sibling.nextSibling; 6860 n = _traverseFullySelected(sibling, how); 6861 6862 if (frag) 6863 frag.appendChild(n); 6864 6865 sibling = nextSibling; 6866 --cnt; 6867 } 6868 6869 n = _traverseRightBoundary(endAncestor, how); 6870 6871 if (frag) 6872 frag.appendChild(n); 6873 6874 if (how != CLONE) { 6875 t.setStartAfter(startAncestor); 6876 t.collapse(TRUE); 6877 } 6878 6879 return frag; 6880 }; 6881 6882 function _traverseRightBoundary(root, how) { 6883 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; 6884 6885 if (next == root) 6886 return _traverseNode(next, isFullySelected, FALSE, how); 6887 6888 parent = next.parentNode; 6889 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 6890 6891 while (parent) { 6892 while (next) { 6893 prevSibling = next.previousSibling; 6894 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 6895 6896 if (how != DELETE) 6897 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 6898 6899 isFullySelected = TRUE; 6900 next = prevSibling; 6901 } 6902 6903 if (parent == root) 6904 return clonedParent; 6905 6906 next = parent.previousSibling; 6907 parent = parent.parentNode; 6908 6909 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 6910 6911 if (how != DELETE) 6912 clonedGrandParent.appendChild(clonedParent); 6913 6914 clonedParent = clonedGrandParent; 6915 } 6916 }; 6917 6918 function _traverseLeftBoundary(root, how) { 6919 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 6920 6921 if (next == root) 6922 return _traverseNode(next, isFullySelected, TRUE, how); 6923 6924 parent = next.parentNode; 6925 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 6926 6927 while (parent) { 6928 while (next) { 6929 nextSibling = next.nextSibling; 6930 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 6931 6932 if (how != DELETE) 6933 clonedParent.appendChild(clonedChild); 6934 6935 isFullySelected = TRUE; 6936 next = nextSibling; 6937 } 6938 6939 if (parent == root) 6940 return clonedParent; 6941 6942 next = parent.nextSibling; 6943 parent = parent.parentNode; 6944 6945 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 6946 6947 if (how != DELETE) 6948 clonedGrandParent.appendChild(clonedParent); 6949 6950 clonedParent = clonedGrandParent; 6951 } 6952 }; 6953 6954 function _traverseNode(n, isFullySelected, isLeft, how) { 6955 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 6956 6957 if (isFullySelected) 6958 return _traverseFullySelected(n, how); 6959 6960 if (n.nodeType == 3 /* TEXT_NODE */) { 6961 txtValue = n.nodeValue; 6962 6963 if (isLeft) { 6964 offset = t[START_OFFSET]; 6965 newNodeValue = txtValue.substring(offset); 6966 oldNodeValue = txtValue.substring(0, offset); 6967 } else { 6968 offset = t[END_OFFSET]; 6969 newNodeValue = txtValue.substring(0, offset); 6970 oldNodeValue = txtValue.substring(offset); 6971 } 6972 6973 if (how != CLONE) 6974 n.nodeValue = oldNodeValue; 6975 6976 if (how == DELETE) 6977 return; 6978 6979 newNode = dom.clone(n, FALSE); 6980 newNode.nodeValue = newNodeValue; 6981 6982 return newNode; 6983 } 6984 6985 if (how == DELETE) 6986 return; 6987 6988 return dom.clone(n, FALSE); 6989 }; 6990 6991 function _traverseFullySelected(n, how) { 6992 if (how != DELETE) 6993 return how == CLONE ? dom.clone(n, TRUE) : n; 6994 6995 n.parentNode.removeChild(n); 6996 }; 6997 6998 function toStringIE() { 6999 return dom.create('body', null, cloneContents()).outerText; 7000 } 7001 7002 return t; 7003 }; 7004 7005 ns.Range = Range; 7006 7007 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 7008 Range.prototype.toString = function() { 7009 return this.toStringIE(); 7010 }; 7011 })(tinymce.dom); 7012 7013 (function() { 7014 function Selection(selection) { 7015 var self = this, dom = selection.dom, TRUE = true, FALSE = false; 7016 7017 function getPosition(rng, start) { 7018 var checkRng, startIndex = 0, endIndex, inside, 7019 children, child, offset, index, position = -1, parent; 7020 7021 // Setup test range, collapse it and get the parent 7022 checkRng = rng.duplicate(); 7023 checkRng.collapse(start); 7024 parent = checkRng.parentElement(); 7025 7026 // Check if the selection is within the right document 7027 if (parent.ownerDocument !== selection.dom.doc) 7028 return; 7029 7030 // IE will report non editable elements as it's parent so look for an editable one 7031 while (parent.contentEditable === "false") { 7032 parent = parent.parentNode; 7033 } 7034 7035 // If parent doesn't have any children then return that we are inside the element 7036 if (!parent.hasChildNodes()) { 7037 return {node : parent, inside : 1}; 7038 } 7039 7040 // Setup node list and endIndex 7041 children = parent.children; 7042 endIndex = children.length - 1; 7043 7044 // Perform a binary search for the position 7045 while (startIndex <= endIndex) { 7046 index = Math.floor((startIndex + endIndex) / 2); 7047 7048 // Move selection to node and compare the ranges 7049 child = children[index]; 7050 checkRng.moveToElementText(child); 7051 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 7052 7053 // Before/after or an exact match 7054 if (position > 0) { 7055 endIndex = index - 1; 7056 } else if (position < 0) { 7057 startIndex = index + 1; 7058 } else { 7059 return {node : child}; 7060 } 7061 } 7062 7063 // Check if child position is before or we didn't find a position 7064 if (position < 0) { 7065 // No element child was found use the parent element and the offset inside that 7066 if (!child) { 7067 checkRng.moveToElementText(parent); 7068 checkRng.collapse(true); 7069 child = parent; 7070 inside = true; 7071 } else 7072 checkRng.collapse(false); 7073 7074 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7075 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 7076 offset = 0; 7077 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7078 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 7079 break; 7080 } 7081 7082 offset++; 7083 } 7084 } else { 7085 // Child position is after the selection endpoint 7086 checkRng.collapse(true); 7087 7088 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one 7089 offset = 0; 7090 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 7091 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 7092 break; 7093 } 7094 7095 offset++; 7096 } 7097 } 7098 7099 return {node : child, position : position, offset : offset, inside : inside}; 7100 }; 7101 7102 // Returns a W3C DOM compatible range object by using the IE Range API 7103 function getRange() { 7104 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; 7105 7106 // If selection is outside the current document just return an empty range 7107 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 7108 if (element.ownerDocument != dom.doc) 7109 return domRange; 7110 7111 collapsed = selection.isCollapsed(); 7112 7113 // Handle control selection 7114 if (ieRange.item) { 7115 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 7116 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 7117 7118 return domRange; 7119 } 7120 7121 function findEndPoint(start) { 7122 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 7123 7124 container = endPoint.node; 7125 offset = endPoint.offset; 7126 7127 if (endPoint.inside && !container.hasChildNodes()) { 7128 domRange[start ? 'setStart' : 'setEnd'](container, 0); 7129 return; 7130 } 7131 7132 if (offset === undef) { 7133 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 7134 return; 7135 } 7136 7137 if (endPoint.position < 0) { 7138 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 7139 7140 if (!sibling) { 7141 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 7142 return; 7143 } 7144 7145 if (!offset) { 7146 if (sibling.nodeType == 3) 7147 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 7148 else 7149 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 7150 7151 return; 7152 } 7153 7154 // Find the text node and offset 7155 while (sibling) { 7156 nodeValue = sibling.nodeValue; 7157 textNodeOffset += nodeValue.length; 7158 7159 // We are at or passed the position we where looking for 7160 if (textNodeOffset >= offset) { 7161 container = sibling; 7162 textNodeOffset -= offset; 7163 textNodeOffset = nodeValue.length - textNodeOffset; 7164 break; 7165 } 7166 7167 sibling = sibling.nextSibling; 7168 } 7169 } else { 7170 // Find the text node and offset 7171 sibling = container.previousSibling; 7172 7173 if (!sibling) 7174 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 7175 7176 // If there isn't any text to loop then use the first position 7177 if (!offset) { 7178 if (container.nodeType == 3) 7179 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 7180 else 7181 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 7182 7183 return; 7184 } 7185 7186 while (sibling) { 7187 textNodeOffset += sibling.nodeValue.length; 7188 7189 // We are at or passed the position we where looking for 7190 if (textNodeOffset >= offset) { 7191 container = sibling; 7192 textNodeOffset -= offset; 7193 break; 7194 } 7195 7196 sibling = sibling.previousSibling; 7197 } 7198 } 7199 7200 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 7201 }; 7202 7203 try { 7204 // Find start point 7205 findEndPoint(true); 7206 7207 // Find end point if needed 7208 if (!collapsed) 7209 findEndPoint(); 7210 } catch (ex) { 7211 // IE has a nasty bug where text nodes might throw "invalid argument" when you 7212 // access the nodeValue or other properties of text nodes. This seems to happend when 7213 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. 7214 if (ex.number == -2147024809) { 7215 // Get the current selection 7216 bookmark = self.getBookmark(2); 7217 7218 // Get start element 7219 tmpRange = ieRange.duplicate(); 7220 tmpRange.collapse(true); 7221 element = tmpRange.parentElement(); 7222 7223 // Get end element 7224 if (!collapsed) { 7225 tmpRange = ieRange.duplicate(); 7226 tmpRange.collapse(false); 7227 element2 = tmpRange.parentElement(); 7228 element2.innerHTML = element2.innerHTML; 7229 } 7230 7231 // Remove the broken elements 7232 element.innerHTML = element.innerHTML; 7233 7234 // Restore the selection 7235 self.moveToBookmark(bookmark); 7236 7237 // Since the range has moved we need to re-get it 7238 ieRange = selection.getRng(); 7239 7240 // Find start point 7241 findEndPoint(true); 7242 7243 // Find end point if needed 7244 if (!collapsed) 7245 findEndPoint(); 7246 } else 7247 throw ex; // Throw other errors 7248 } 7249 7250 return domRange; 7251 }; 7252 7253 this.getBookmark = function(type) { 7254 var rng = selection.getRng(), start, end, bookmark = {}; 7255 7256 function getIndexes(node) { 7257 var parent, root, children, i, indexes = []; 7258 7259 parent = node.parentNode; 7260 root = dom.getRoot().parentNode; 7261 7262 while (parent != root && parent.nodeType !== 9) { 7263 children = parent.children; 7264 7265 i = children.length; 7266 while (i--) { 7267 if (node === children[i]) { 7268 indexes.push(i); 7269 break; 7270 } 7271 } 7272 7273 node = parent; 7274 parent = parent.parentNode; 7275 } 7276 7277 return indexes; 7278 }; 7279 7280 function getBookmarkEndPoint(start) { 7281 var position; 7282 7283 position = getPosition(rng, start); 7284 if (position) { 7285 return { 7286 position : position.position, 7287 offset : position.offset, 7288 indexes : getIndexes(position.node), 7289 inside : position.inside 7290 }; 7291 } 7292 }; 7293 7294 // Non ubstructive bookmark 7295 if (type === 2) { 7296 // Handle text selection 7297 if (!rng.item) { 7298 bookmark.start = getBookmarkEndPoint(true); 7299 7300 if (!selection.isCollapsed()) 7301 bookmark.end = getBookmarkEndPoint(); 7302 } else 7303 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; 7304 } 7305 7306 return bookmark; 7307 }; 7308 7309 this.moveToBookmark = function(bookmark) { 7310 var rng, body = dom.doc.body; 7311 7312 function resolveIndexes(indexes) { 7313 var node, i, idx, children; 7314 7315 node = dom.getRoot(); 7316 for (i = indexes.length - 1; i >= 0; i--) { 7317 children = node.children; 7318 idx = indexes[i]; 7319 7320 if (idx <= children.length - 1) { 7321 node = children[idx]; 7322 } 7323 } 7324 7325 return node; 7326 }; 7327 7328 function setBookmarkEndPoint(start) { 7329 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; 7330 7331 if (endPoint) { 7332 moveLeft = endPoint.position > 0; 7333 7334 moveRng = body.createTextRange(); 7335 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 7336 7337 offset = endPoint.offset; 7338 if (offset !== undef) { 7339 moveRng.collapse(endPoint.inside || moveLeft); 7340 moveRng.moveStart('character', moveLeft ? -offset : offset); 7341 } else 7342 moveRng.collapse(start); 7343 7344 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 7345 7346 if (start) 7347 rng.collapse(true); 7348 } 7349 }; 7350 7351 if (bookmark.start) { 7352 if (bookmark.start.ctrl) { 7353 rng = body.createControlRange(); 7354 rng.addElement(resolveIndexes(bookmark.start.indexes)); 7355 rng.select(); 7356 } else { 7357 rng = body.createTextRange(); 7358 setBookmarkEndPoint(true); 7359 setBookmarkEndPoint(); 7360 rng.select(); 7361 } 7362 } 7363 }; 7364 7365 this.addRange = function(rng) { 7366 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body; 7367 7368 function setEndPoint(start) { 7369 var container, offset, marker, tmpRng, nodes; 7370 7371 marker = dom.create('a'); 7372 container = start ? startContainer : endContainer; 7373 offset = start ? startOffset : endOffset; 7374 tmpRng = ieRng.duplicate(); 7375 7376 if (container == doc || container == doc.documentElement) { 7377 container = body; 7378 offset = 0; 7379 } 7380 7381 if (container.nodeType == 3) { 7382 container.parentNode.insertBefore(marker, container); 7383 tmpRng.moveToElementText(marker); 7384 tmpRng.moveStart('character', offset); 7385 dom.remove(marker); 7386 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7387 } else { 7388 nodes = container.childNodes; 7389 7390 if (nodes.length) { 7391 if (offset >= nodes.length) { 7392 dom.insertAfter(marker, nodes[nodes.length - 1]); 7393 } else { 7394 container.insertBefore(marker, nodes[offset]); 7395 } 7396 7397 tmpRng.moveToElementText(marker); 7398 } else if (container.canHaveHTML) { 7399 // Empty node selection for example <div>|</div> 7400 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 7401 container.innerHTML = '<span>\uFEFF</span>'; 7402 marker = container.firstChild; 7403 tmpRng.moveToElementText(marker); 7404 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 7405 } 7406 7407 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 7408 dom.remove(marker); 7409 } 7410 } 7411 7412 // Setup some shorter versions 7413 startContainer = rng.startContainer; 7414 startOffset = rng.startOffset; 7415 endContainer = rng.endContainer; 7416 endOffset = rng.endOffset; 7417 ieRng = body.createTextRange(); 7418 7419 // If single element selection then try making a control selection out of it 7420 if (startContainer == endContainer && startContainer.nodeType == 1) { 7421 // Trick to place the caret inside an empty block element like <p></p> 7422 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 7423 if (startContainer.canHaveHTML) { 7424 // Check if previous sibling is an empty block if it is then we need to render it 7425 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 7426 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 7427 sibling = startContainer.previousSibling; 7428 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 7429 sibling.innerHTML = '\uFEFF'; 7430 } else { 7431 sibling = null; 7432 } 7433 7434 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>'; 7435 ieRng.moveToElementText(startContainer.lastChild); 7436 ieRng.select(); 7437 dom.doc.selection.clear(); 7438 startContainer.innerHTML = ''; 7439 7440 if (sibling) { 7441 sibling.innerHTML = ''; 7442 } 7443 return; 7444 } else { 7445 startOffset = dom.nodeIndex(startContainer); 7446 startContainer = startContainer.parentNode; 7447 } 7448 } 7449 7450 if (startOffset == endOffset - 1) { 7451 try { 7452 ctrlRng = body.createControlRange(); 7453 ctrlRng.addElement(startContainer.childNodes[startOffset]); 7454 ctrlRng.select(); 7455 return; 7456 } catch (ex) { 7457 // Ignore 7458 } 7459 } 7460 } 7461 7462 // Set start/end point of selection 7463 setEndPoint(true); 7464 setEndPoint(); 7465 7466 // Select the new range and scroll it into view 7467 ieRng.select(); 7468 }; 7469 7470 // Expose range method 7471 this.getRangeAt = getRange; 7472 }; 7473 7474 // Expose the selection object 7475 tinymce.dom.TridentSelection = Selection; 7476 })(); 7477 7478 7479 /* 7480 * Sizzle CSS Selector Engine 7481 * Copyright, The Dojo Foundation 7482 * Released under the MIT, BSD, and GPL Licenses. 7483 * More information: http://sizzlejs.com/ 7484 */ 7485 (function(){ 7486 7487 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 7488 expando = "sizcache", 7489 done = 0, 7490 toString = Object.prototype.toString, 7491 hasDuplicate = false, 7492 baseHasDuplicate = true, 7493 rBackslash = /\\/g, 7494 rReturn = /\r\n/g, 7495 rNonWord = /\W/; 7496 7497 // Here we check if the JavaScript engine is using some sort of 7498 // optimization where it does not always call our comparision 7499 // function. If that is the case, discard the hasDuplicate value. 7500 // Thus far that includes Google Chrome. 7501 [0, 0].sort(function() { 7502 baseHasDuplicate = false; 7503 return 0; 7504 }); 7505 7506 var Sizzle = function( selector, context, results, seed ) { 7507 results = results || []; 7508 context = context || document; 7509 7510 var origContext = context; 7511 7512 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 7513 return []; 7514 } 7515 7516 if ( !selector || typeof selector !== "string" ) { 7517 return results; 7518 } 7519 7520 var m, set, checkSet, extra, ret, cur, pop, i, 7521 prune = true, 7522 contextXML = Sizzle.isXML( context ), 7523 parts = [], 7524 soFar = selector; 7525 7526 // Reset the position of the chunker regexp (start from head) 7527 do { 7528 chunker.exec( "" ); 7529 m = chunker.exec( soFar ); 7530 7531 if ( m ) { 7532 soFar = m[3]; 7533 7534 parts.push( m[1] ); 7535 7536 if ( m[2] ) { 7537 extra = m[3]; 7538 break; 7539 } 7540 } 7541 } while ( m ); 7542 7543 if ( parts.length > 1 && origPOS.exec( selector ) ) { 7544 7545 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 7546 set = posProcess( parts[0] + parts[1], context, seed ); 7547 7548 } else { 7549 set = Expr.relative[ parts[0] ] ? 7550 [ context ] : 7551 Sizzle( parts.shift(), context ); 7552 7553 while ( parts.length ) { 7554 selector = parts.shift(); 7555 7556 if ( Expr.relative[ selector ] ) { 7557 selector += parts.shift(); 7558 } 7559 7560 set = posProcess( selector, set, seed ); 7561 } 7562 } 7563 7564 } else { 7565 // Take a shortcut and set the context if the root selector is an ID 7566 // (but not if it'll be faster if the inner selector is an ID) 7567 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 7568 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 7569 7570 ret = Sizzle.find( parts.shift(), context, contextXML ); 7571 context = ret.expr ? 7572 Sizzle.filter( ret.expr, ret.set )[0] : 7573 ret.set[0]; 7574 } 7575 7576 if ( context ) { 7577 ret = seed ? 7578 { expr: parts.pop(), set: makeArray(seed) } : 7579 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 7580 7581 set = ret.expr ? 7582 Sizzle.filter( ret.expr, ret.set ) : 7583 ret.set; 7584 7585 if ( parts.length > 0 ) { 7586 checkSet = makeArray( set ); 7587 7588 } else { 7589 prune = false; 7590 } 7591 7592 while ( parts.length ) { 7593 cur = parts.pop(); 7594 pop = cur; 7595 7596 if ( !Expr.relative[ cur ] ) { 7597 cur = ""; 7598 } else { 7599 pop = parts.pop(); 7600 } 7601 7602 if ( pop == null ) { 7603 pop = context; 7604 } 7605 7606 Expr.relative[ cur ]( checkSet, pop, contextXML ); 7607 } 7608 7609 } else { 7610 checkSet = parts = []; 7611 } 7612 } 7613 7614 if ( !checkSet ) { 7615 checkSet = set; 7616 } 7617 7618 if ( !checkSet ) { 7619 Sizzle.error( cur || selector ); 7620 } 7621 7622 if ( toString.call(checkSet) === "[object Array]" ) { 7623 if ( !prune ) { 7624 results.push.apply( results, checkSet ); 7625 7626 } else if ( context && context.nodeType === 1 ) { 7627 for ( i = 0; checkSet[i] != null; i++ ) { 7628 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 7629 results.push( set[i] ); 7630 } 7631 } 7632 7633 } else { 7634 for ( i = 0; checkSet[i] != null; i++ ) { 7635 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 7636 results.push( set[i] ); 7637 } 7638 } 7639 } 7640 7641 } else { 7642 makeArray( checkSet, results ); 7643 } 7644 7645 if ( extra ) { 7646 Sizzle( extra, origContext, results, seed ); 7647 Sizzle.uniqueSort( results ); 7648 } 7649 7650 return results; 7651 }; 7652 7653 Sizzle.uniqueSort = function( results ) { 7654 if ( sortOrder ) { 7655 hasDuplicate = baseHasDuplicate; 7656 results.sort( sortOrder ); 7657 7658 if ( hasDuplicate ) { 7659 for ( var i = 1; i < results.length; i++ ) { 7660 if ( results[i] === results[ i - 1 ] ) { 7661 results.splice( i--, 1 ); 7662 } 7663 } 7664 } 7665 } 7666 7667 return results; 7668 }; 7669 7670 Sizzle.matches = function( expr, set ) { 7671 return Sizzle( expr, null, null, set ); 7672 }; 7673 7674 Sizzle.matchesSelector = function( node, expr ) { 7675 return Sizzle( expr, null, null, [node] ).length > 0; 7676 }; 7677 7678 Sizzle.find = function( expr, context, isXML ) { 7679 var set, i, len, match, type, left; 7680 7681 if ( !expr ) { 7682 return []; 7683 } 7684 7685 for ( i = 0, len = Expr.order.length; i < len; i++ ) { 7686 type = Expr.order[i]; 7687 7688 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 7689 left = match[1]; 7690 match.splice( 1, 1 ); 7691 7692 if ( left.substr( left.length - 1 ) !== "\\" ) { 7693 match[1] = (match[1] || "").replace( rBackslash, "" ); 7694 set = Expr.find[ type ]( match, context, isXML ); 7695 7696 if ( set != null ) { 7697 expr = expr.replace( Expr.match[ type ], "" ); 7698 break; 7699 } 7700 } 7701 } 7702 } 7703 7704 if ( !set ) { 7705 set = typeof context.getElementsByTagName !== "undefined" ? 7706 context.getElementsByTagName( "*" ) : 7707 []; 7708 } 7709 7710 return { set: set, expr: expr }; 7711 }; 7712 7713 Sizzle.filter = function( expr, set, inplace, not ) { 7714 var match, anyFound, 7715 type, found, item, filter, left, 7716 i, pass, 7717 old = expr, 7718 result = [], 7719 curLoop = set, 7720 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 7721 7722 while ( expr && set.length ) { 7723 for ( type in Expr.filter ) { 7724 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 7725 filter = Expr.filter[ type ]; 7726 left = match[1]; 7727 7728 anyFound = false; 7729 7730 match.splice(1,1); 7731 7732 if ( left.substr( left.length - 1 ) === "\\" ) { 7733 continue; 7734 } 7735 7736 if ( curLoop === result ) { 7737 result = []; 7738 } 7739 7740 if ( Expr.preFilter[ type ] ) { 7741 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 7742 7743 if ( !match ) { 7744 anyFound = found = true; 7745 7746 } else if ( match === true ) { 7747 continue; 7748 } 7749 } 7750 7751 if ( match ) { 7752 for ( i = 0; (item = curLoop[i]) != null; i++ ) { 7753 if ( item ) { 7754 found = filter( item, match, i, curLoop ); 7755 pass = not ^ found; 7756 7757 if ( inplace && found != null ) { 7758 if ( pass ) { 7759 anyFound = true; 7760 7761 } else { 7762 curLoop[i] = false; 7763 } 7764 7765 } else if ( pass ) { 7766 result.push( item ); 7767 anyFound = true; 7768 } 7769 } 7770 } 7771 } 7772 7773 if ( found !== undefined ) { 7774 if ( !inplace ) { 7775 curLoop = result; 7776 } 7777 7778 expr = expr.replace( Expr.match[ type ], "" ); 7779 7780 if ( !anyFound ) { 7781 return []; 7782 } 7783 7784 break; 7785 } 7786 } 7787 } 7788 7789 // Improper expression 7790 if ( expr === old ) { 7791 if ( anyFound == null ) { 7792 Sizzle.error( expr ); 7793 7794 } else { 7795 break; 7796 } 7797 } 7798 7799 old = expr; 7800 } 7801 7802 return curLoop; 7803 }; 7804 7805 Sizzle.error = function( msg ) { 7806 throw new Error( "Syntax error, unrecognized expression: " + msg ); 7807 }; 7808 7809 var getText = Sizzle.getText = function( elem ) { 7810 var i, node, 7811 nodeType = elem.nodeType, 7812 ret = ""; 7813 7814 if ( nodeType ) { 7815 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { 7816 // Use textContent || innerText for elements 7817 if ( typeof elem.textContent === 'string' ) { 7818 return elem.textContent; 7819 } else if ( typeof elem.innerText === 'string' ) { 7820 // Replace IE's carriage returns 7821 return elem.innerText.replace( rReturn, '' ); 7822 } else { 7823 // Traverse it's children 7824 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { 7825 ret += getText( elem ); 7826 } 7827 } 7828 } else if ( nodeType === 3 || nodeType === 4 ) { 7829 return elem.nodeValue; 7830 } 7831 } else { 7832 7833 // If no nodeType, this is expected to be an array 7834 for ( i = 0; (node = elem[i]); i++ ) { 7835 // Do not traverse comment nodes 7836 if ( node.nodeType !== 8 ) { 7837 ret += getText( node ); 7838 } 7839 } 7840 } 7841 return ret; 7842 }; 7843 7844 var Expr = Sizzle.selectors = { 7845 order: [ "ID", "NAME", "TAG" ], 7846 7847 match: { 7848 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7849 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 7850 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 7851 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 7852 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 7853 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 7854 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 7855 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 7856 }, 7857 7858 leftMatch: {}, 7859 7860 attrMap: { 7861 "class": "className", 7862 "for": "htmlFor" 7863 }, 7864 7865 attrHandle: { 7866 href: function( elem ) { 7867 return elem.getAttribute( "href" ); 7868 }, 7869 type: function( elem ) { 7870 return elem.getAttribute( "type" ); 7871 } 7872 }, 7873 7874 relative: { 7875 "+": function(checkSet, part){ 7876 var isPartStr = typeof part === "string", 7877 isTag = isPartStr && !rNonWord.test( part ), 7878 isPartStrNotTag = isPartStr && !isTag; 7879 7880 if ( isTag ) { 7881 part = part.toLowerCase(); 7882 } 7883 7884 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 7885 if ( (elem = checkSet[i]) ) { 7886 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 7887 7888 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 7889 elem || false : 7890 elem === part; 7891 } 7892 } 7893 7894 if ( isPartStrNotTag ) { 7895 Sizzle.filter( part, checkSet, true ); 7896 } 7897 }, 7898 7899 ">": function( checkSet, part ) { 7900 var elem, 7901 isPartStr = typeof part === "string", 7902 i = 0, 7903 l = checkSet.length; 7904 7905 if ( isPartStr && !rNonWord.test( part ) ) { 7906 part = part.toLowerCase(); 7907 7908 for ( ; i < l; i++ ) { 7909 elem = checkSet[i]; 7910 7911 if ( elem ) { 7912 var parent = elem.parentNode; 7913 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 7914 } 7915 } 7916 7917 } else { 7918 for ( ; i < l; i++ ) { 7919 elem = checkSet[i]; 7920 7921 if ( elem ) { 7922 checkSet[i] = isPartStr ? 7923 elem.parentNode : 7924 elem.parentNode === part; 7925 } 7926 } 7927 7928 if ( isPartStr ) { 7929 Sizzle.filter( part, checkSet, true ); 7930 } 7931 } 7932 }, 7933 7934 "": function(checkSet, part, isXML){ 7935 var nodeCheck, 7936 doneName = done++, 7937 checkFn = dirCheck; 7938 7939 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7940 part = part.toLowerCase(); 7941 nodeCheck = part; 7942 checkFn = dirNodeCheck; 7943 } 7944 7945 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 7946 }, 7947 7948 "~": function( checkSet, part, isXML ) { 7949 var nodeCheck, 7950 doneName = done++, 7951 checkFn = dirCheck; 7952 7953 if ( typeof part === "string" && !rNonWord.test( part ) ) { 7954 part = part.toLowerCase(); 7955 nodeCheck = part; 7956 checkFn = dirNodeCheck; 7957 } 7958 7959 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 7960 } 7961 }, 7962 7963 find: { 7964 ID: function( match, context, isXML ) { 7965 if ( typeof context.getElementById !== "undefined" && !isXML ) { 7966 var m = context.getElementById(match[1]); 7967 // Check parentNode to catch when Blackberry 4.6 returns 7968 // nodes that are no longer in the document #6963 7969 return m && m.parentNode ? [m] : []; 7970 } 7971 }, 7972 7973 NAME: function( match, context ) { 7974 if ( typeof context.getElementsByName !== "undefined" ) { 7975 var ret = [], 7976 results = context.getElementsByName( match[1] ); 7977 7978 for ( var i = 0, l = results.length; i < l; i++ ) { 7979 if ( results[i].getAttribute("name") === match[1] ) { 7980 ret.push( results[i] ); 7981 } 7982 } 7983 7984 return ret.length === 0 ? null : ret; 7985 } 7986 }, 7987 7988 TAG: function( match, context ) { 7989 if ( typeof context.getElementsByTagName !== "undefined" ) { 7990 return context.getElementsByTagName( match[1] ); 7991 } 7992 } 7993 }, 7994 preFilter: { 7995 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 7996 match = " " + match[1].replace( rBackslash, "" ) + " "; 7997 7998 if ( isXML ) { 7999 return match; 8000 } 8001 8002 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 8003 if ( elem ) { 8004 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 8005 if ( !inplace ) { 8006 result.push( elem ); 8007 } 8008 8009 } else if ( inplace ) { 8010 curLoop[i] = false; 8011 } 8012 } 8013 } 8014 8015 return false; 8016 }, 8017 8018 ID: function( match ) { 8019 return match[1].replace( rBackslash, "" ); 8020 }, 8021 8022 TAG: function( match, curLoop ) { 8023 return match[1].replace( rBackslash, "" ).toLowerCase(); 8024 }, 8025 8026 CHILD: function( match ) { 8027 if ( match[1] === "nth" ) { 8028 if ( !match[2] ) { 8029 Sizzle.error( match[0] ); 8030 } 8031 8032 match[2] = match[2].replace(/^\+|\s*/g, ''); 8033 8034 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 8035 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 8036 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 8037 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 8038 8039 // calculate the numbers (first)n+(last) including if they are negative 8040 match[2] = (test[1] + (test[2] || 1)) - 0; 8041 match[3] = test[3] - 0; 8042 } 8043 else if ( match[2] ) { 8044 Sizzle.error( match[0] ); 8045 } 8046 8047 // TODO: Move to normal caching system 8048 match[0] = done++; 8049 8050 return match; 8051 }, 8052 8053 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 8054 var name = match[1] = match[1].replace( rBackslash, "" ); 8055 8056 if ( !isXML && Expr.attrMap[name] ) { 8057 match[1] = Expr.attrMap[name]; 8058 } 8059 8060 // Handle if an un-quoted value was used 8061 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 8062 8063 if ( match[2] === "~=" ) { 8064 match[4] = " " + match[4] + " "; 8065 } 8066 8067 return match; 8068 }, 8069 8070 PSEUDO: function( match, curLoop, inplace, result, not ) { 8071 if ( match[1] === "not" ) { 8072 // If we're dealing with a complex expression, or a simple one 8073 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 8074 match[3] = Sizzle(match[3], null, null, curLoop); 8075 8076 } else { 8077 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 8078 8079 if ( !inplace ) { 8080 result.push.apply( result, ret ); 8081 } 8082 8083 return false; 8084 } 8085 8086 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 8087 return true; 8088 } 8089 8090 return match; 8091 }, 8092 8093 POS: function( match ) { 8094 match.unshift( true ); 8095 8096 return match; 8097 } 8098 }, 8099 8100 filters: { 8101 enabled: function( elem ) { 8102 return elem.disabled === false && elem.type !== "hidden"; 8103 }, 8104 8105 disabled: function( elem ) { 8106 return elem.disabled === true; 8107 }, 8108 8109 checked: function( elem ) { 8110 return elem.checked === true; 8111 }, 8112 8113 selected: function( elem ) { 8114 // Accessing this property makes selected-by-default 8115 // options in Safari work properly 8116 if ( elem.parentNode ) { 8117 elem.parentNode.selectedIndex; 8118 } 8119 8120 return elem.selected === true; 8121 }, 8122 8123 parent: function( elem ) { 8124 return !!elem.firstChild; 8125 }, 8126 8127 empty: function( elem ) { 8128 return !elem.firstChild; 8129 }, 8130 8131 has: function( elem, i, match ) { 8132 return !!Sizzle( match[3], elem ).length; 8133 }, 8134 8135 header: function( elem ) { 8136 return (/h\d/i).test( elem.nodeName ); 8137 }, 8138 8139 text: function( elem ) { 8140 var attr = elem.getAttribute( "type" ), type = elem.type; 8141 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 8142 // use getAttribute instead to test this case 8143 return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); 8144 }, 8145 8146 radio: function( elem ) { 8147 return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; 8148 }, 8149 8150 checkbox: function( elem ) { 8151 return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; 8152 }, 8153 8154 file: function( elem ) { 8155 return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; 8156 }, 8157 8158 password: function( elem ) { 8159 return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; 8160 }, 8161 8162 submit: function( elem ) { 8163 var name = elem.nodeName.toLowerCase(); 8164 return (name === "input" || name === "button") && "submit" === elem.type; 8165 }, 8166 8167 image: function( elem ) { 8168 return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; 8169 }, 8170 8171 reset: function( elem ) { 8172 var name = elem.nodeName.toLowerCase(); 8173 return (name === "input" || name === "button") && "reset" === elem.type; 8174 }, 8175 8176 button: function( elem ) { 8177 var name = elem.nodeName.toLowerCase(); 8178 return name === "input" && "button" === elem.type || name === "button"; 8179 }, 8180 8181 input: function( elem ) { 8182 return (/input|select|textarea|button/i).test( elem.nodeName ); 8183 }, 8184 8185 focus: function( elem ) { 8186 return elem === elem.ownerDocument.activeElement; 8187 } 8188 }, 8189 setFilters: { 8190 first: function( elem, i ) { 8191 return i === 0; 8192 }, 8193 8194 last: function( elem, i, match, array ) { 8195 return i === array.length - 1; 8196 }, 8197 8198 even: function( elem, i ) { 8199 return i % 2 === 0; 8200 }, 8201 8202 odd: function( elem, i ) { 8203 return i % 2 === 1; 8204 }, 8205 8206 lt: function( elem, i, match ) { 8207 return i < match[3] - 0; 8208 }, 8209 8210 gt: function( elem, i, match ) { 8211 return i > match[3] - 0; 8212 }, 8213 8214 nth: function( elem, i, match ) { 8215 return match[3] - 0 === i; 8216 }, 8217 8218 eq: function( elem, i, match ) { 8219 return match[3] - 0 === i; 8220 } 8221 }, 8222 filter: { 8223 PSEUDO: function( elem, match, i, array ) { 8224 var name = match[1], 8225 filter = Expr.filters[ name ]; 8226 8227 if ( filter ) { 8228 return filter( elem, i, match, array ); 8229 8230 } else if ( name === "contains" ) { 8231 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 8232 8233 } else if ( name === "not" ) { 8234 var not = match[3]; 8235 8236 for ( var j = 0, l = not.length; j < l; j++ ) { 8237 if ( not[j] === elem ) { 8238 return false; 8239 } 8240 } 8241 8242 return true; 8243 8244 } else { 8245 Sizzle.error( name ); 8246 } 8247 }, 8248 8249 CHILD: function( elem, match ) { 8250 var first, last, 8251 doneName, parent, cache, 8252 count, diff, 8253 type = match[1], 8254 node = elem; 8255 8256 switch ( type ) { 8257 case "only": 8258 case "first": 8259 while ( (node = node.previousSibling) ) { 8260 if ( node.nodeType === 1 ) { 8261 return false; 8262 } 8263 } 8264 8265 if ( type === "first" ) { 8266 return true; 8267 } 8268 8269 node = elem; 8270 8271 /* falls through */ 8272 case "last": 8273 while ( (node = node.nextSibling) ) { 8274 if ( node.nodeType === 1 ) { 8275 return false; 8276 } 8277 } 8278 8279 return true; 8280 8281 case "nth": 8282 first = match[2]; 8283 last = match[3]; 8284 8285 if ( first === 1 && last === 0 ) { 8286 return true; 8287 } 8288 8289 doneName = match[0]; 8290 parent = elem.parentNode; 8291 8292 if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { 8293 count = 0; 8294 8295 for ( node = parent.firstChild; node; node = node.nextSibling ) { 8296 if ( node.nodeType === 1 ) { 8297 node.nodeIndex = ++count; 8298 } 8299 } 8300 8301 parent[ expando ] = doneName; 8302 } 8303 8304 diff = elem.nodeIndex - last; 8305 8306 if ( first === 0 ) { 8307 return diff === 0; 8308 8309 } else { 8310 return ( diff % first === 0 && diff / first >= 0 ); 8311 } 8312 } 8313 }, 8314 8315 ID: function( elem, match ) { 8316 return elem.nodeType === 1 && elem.getAttribute("id") === match; 8317 }, 8318 8319 TAG: function( elem, match ) { 8320 return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; 8321 }, 8322 8323 CLASS: function( elem, match ) { 8324 return (" " + (elem.className || elem.getAttribute("class")) + " ") 8325 .indexOf( match ) > -1; 8326 }, 8327 8328 ATTR: function( elem, match ) { 8329 var name = match[1], 8330 result = Sizzle.attr ? 8331 Sizzle.attr( elem, name ) : 8332 Expr.attrHandle[ name ] ? 8333 Expr.attrHandle[ name ]( elem ) : 8334 elem[ name ] != null ? 8335 elem[ name ] : 8336 elem.getAttribute( name ), 8337 value = result + "", 8338 type = match[2], 8339 check = match[4]; 8340 8341 return result == null ? 8342 type === "!=" : 8343 !type && Sizzle.attr ? 8344 result != null : 8345 type === "=" ? 8346 value === check : 8347 type === "*=" ? 8348 value.indexOf(check) >= 0 : 8349 type === "~=" ? 8350 (" " + value + " ").indexOf(check) >= 0 : 8351 !check ? 8352 value && result !== false : 8353 type === "!=" ? 8354 value !== check : 8355 type === "^=" ? 8356 value.indexOf(check) === 0 : 8357 type === "$=" ? 8358 value.substr(value.length - check.length) === check : 8359 type === "|=" ? 8360 value === check || value.substr(0, check.length + 1) === check + "-" : 8361 false; 8362 }, 8363 8364 POS: function( elem, match, i, array ) { 8365 var name = match[2], 8366 filter = Expr.setFilters[ name ]; 8367 8368 if ( filter ) { 8369 return filter( elem, i, match, array ); 8370 } 8371 } 8372 } 8373 }; 8374 8375 var origPOS = Expr.match.POS, 8376 fescape = function(all, num){ 8377 return "\\" + (num - 0 + 1); 8378 }; 8379 8380 for ( var type in Expr.match ) { 8381 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 8382 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 8383 } 8384 // Expose origPOS 8385 // "global" as in regardless of relation to brackets/parens 8386 Expr.match.globalPOS = origPOS; 8387 8388 var makeArray = function( array, results ) { 8389 array = Array.prototype.slice.call( array, 0 ); 8390 8391 if ( results ) { 8392 results.push.apply( results, array ); 8393 return results; 8394 } 8395 8396 return array; 8397 }; 8398 8399 // Perform a simple check to determine if the browser is capable of 8400 // converting a NodeList to an array using builtin methods. 8401 // Also verifies that the returned array holds DOM nodes 8402 // (which is not the case in the Blackberry browser) 8403 try { 8404 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 8405 8406 // Provide a fallback method if it does not work 8407 } catch( e ) { 8408 makeArray = function( array, results ) { 8409 var i = 0, 8410 ret = results || []; 8411 8412 if ( toString.call(array) === "[object Array]" ) { 8413 Array.prototype.push.apply( ret, array ); 8414 8415 } else { 8416 if ( typeof array.length === "number" ) { 8417 for ( var l = array.length; i < l; i++ ) { 8418 ret.push( array[i] ); 8419 } 8420 8421 } else { 8422 for ( ; array[i]; i++ ) { 8423 ret.push( array[i] ); 8424 } 8425 } 8426 } 8427 8428 return ret; 8429 }; 8430 } 8431 8432 var sortOrder, siblingCheck; 8433 8434 if ( document.documentElement.compareDocumentPosition ) { 8435 sortOrder = function( a, b ) { 8436 if ( a === b ) { 8437 hasDuplicate = true; 8438 return 0; 8439 } 8440 8441 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 8442 return a.compareDocumentPosition ? -1 : 1; 8443 } 8444 8445 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 8446 }; 8447 8448 } else { 8449 sortOrder = function( a, b ) { 8450 // The nodes are identical, we can exit early 8451 if ( a === b ) { 8452 hasDuplicate = true; 8453 return 0; 8454 8455 // Fallback to using sourceIndex (in IE) if it's available on both nodes 8456 } else if ( a.sourceIndex && b.sourceIndex ) { 8457 return a.sourceIndex - b.sourceIndex; 8458 } 8459 8460 var al, bl, 8461 ap = [], 8462 bp = [], 8463 aup = a.parentNode, 8464 bup = b.parentNode, 8465 cur = aup; 8466 8467 // If the nodes are siblings (or identical) we can do a quick check 8468 if ( aup === bup ) { 8469 return siblingCheck( a, b ); 8470 8471 // If no parents were found then the nodes are disconnected 8472 } else if ( !aup ) { 8473 return -1; 8474 8475 } else if ( !bup ) { 8476 return 1; 8477 } 8478 8479 // Otherwise they're somewhere else in the tree so we need 8480 // to build up a full list of the parentNodes for comparison 8481 while ( cur ) { 8482 ap.unshift( cur ); 8483 cur = cur.parentNode; 8484 } 8485 8486 cur = bup; 8487 8488 while ( cur ) { 8489 bp.unshift( cur ); 8490 cur = cur.parentNode; 8491 } 8492 8493 al = ap.length; 8494 bl = bp.length; 8495 8496 // Start walking down the tree looking for a discrepancy 8497 for ( var i = 0; i < al && i < bl; i++ ) { 8498 if ( ap[i] !== bp[i] ) { 8499 return siblingCheck( ap[i], bp[i] ); 8500 } 8501 } 8502 8503 // We ended someplace up the tree so do a sibling check 8504 return i === al ? 8505 siblingCheck( a, bp[i], -1 ) : 8506 siblingCheck( ap[i], b, 1 ); 8507 }; 8508 8509 siblingCheck = function( a, b, ret ) { 8510 if ( a === b ) { 8511 return ret; 8512 } 8513 8514 var cur = a.nextSibling; 8515 8516 while ( cur ) { 8517 if ( cur === b ) { 8518 return -1; 8519 } 8520 8521 cur = cur.nextSibling; 8522 } 8523 8524 return 1; 8525 }; 8526 } 8527 8528 // Check to see if the browser returns elements by name when 8529 // querying by getElementById (and provide a workaround) 8530 (function(){ 8531 // We're going to inject a fake input element with a specified name 8532 var form = document.createElement("div"), 8533 id = "script" + (new Date()).getTime(), 8534 root = document.documentElement; 8535 8536 form.innerHTML = "<a name='" + id + "'/>"; 8537 8538 // Inject it into the root element, check its status, and remove it quickly 8539 root.insertBefore( form, root.firstChild ); 8540 8541 // The workaround has to do additional checks after a getElementById 8542 // Which slows things down for other browsers (hence the branching) 8543 if ( document.getElementById( id ) ) { 8544 Expr.find.ID = function( match, context, isXML ) { 8545 if ( typeof context.getElementById !== "undefined" && !isXML ) { 8546 var m = context.getElementById(match[1]); 8547 8548 return m ? 8549 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 8550 [m] : 8551 undefined : 8552 []; 8553 } 8554 }; 8555 8556 Expr.filter.ID = function( elem, match ) { 8557 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 8558 8559 return elem.nodeType === 1 && node && node.nodeValue === match; 8560 }; 8561 } 8562 8563 root.removeChild( form ); 8564 8565 // release memory in IE 8566 root = form = null; 8567 })(); 8568 8569 (function(){ 8570 // Check to see if the browser returns only elements 8571 // when doing getElementsByTagName("*") 8572 8573 // Create a fake element 8574 var div = document.createElement("div"); 8575 div.appendChild( document.createComment("") ); 8576 8577 // Make sure no comments are found 8578 if ( div.getElementsByTagName("*").length > 0 ) { 8579 Expr.find.TAG = function( match, context ) { 8580 var results = context.getElementsByTagName( match[1] ); 8581 8582 // Filter out possible comments 8583 if ( match[1] === "*" ) { 8584 var tmp = []; 8585 8586 for ( var i = 0; results[i]; i++ ) { 8587 if ( results[i].nodeType === 1 ) { 8588 tmp.push( results[i] ); 8589 } 8590 } 8591 8592 results = tmp; 8593 } 8594 8595 return results; 8596 }; 8597 } 8598 8599 // Check to see if an attribute returns normalized href attributes 8600 div.innerHTML = "<a href='#'></a>"; 8601 8602 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 8603 div.firstChild.getAttribute("href") !== "#" ) { 8604 8605 Expr.attrHandle.href = function( elem ) { 8606 return elem.getAttribute( "href", 2 ); 8607 }; 8608 } 8609 8610 // release memory in IE 8611 div = null; 8612 })(); 8613 8614 if ( document.querySelectorAll ) { 8615 (function(){ 8616 var oldSizzle = Sizzle, 8617 div = document.createElement("div"), 8618 id = "__sizzle__"; 8619 8620 div.innerHTML = "<p class='TEST'></p>"; 8621 8622 // Safari can't handle uppercase or unicode characters when 8623 // in quirks mode. 8624 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 8625 return; 8626 } 8627 8628 Sizzle = function( query, context, extra, seed ) { 8629 context = context || document; 8630 8631 // Only use querySelectorAll on non-XML documents 8632 // (ID selectors don't work in non-HTML documents) 8633 if ( !seed && !Sizzle.isXML(context) ) { 8634 // See if we find a selector to speed up 8635 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 8636 8637 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 8638 // Speed-up: Sizzle("TAG") 8639 if ( match[1] ) { 8640 return makeArray( context.getElementsByTagName( query ), extra ); 8641 8642 // Speed-up: Sizzle(".CLASS") 8643 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 8644 return makeArray( context.getElementsByClassName( match[2] ), extra ); 8645 } 8646 } 8647 8648 if ( context.nodeType === 9 ) { 8649 // Speed-up: Sizzle("body") 8650 // The body element only exists once, optimize finding it 8651 if ( query === "body" && context.body ) { 8652 return makeArray( [ context.body ], extra ); 8653 8654 // Speed-up: Sizzle("#ID") 8655 } else if ( match && match[3] ) { 8656 var elem = context.getElementById( match[3] ); 8657 8658 // Check parentNode to catch when Blackberry 4.6 returns 8659 // nodes that are no longer in the document #6963 8660 if ( elem && elem.parentNode ) { 8661 // Handle the case where IE and Opera return items 8662 // by name instead of ID 8663 if ( elem.id === match[3] ) { 8664 return makeArray( [ elem ], extra ); 8665 } 8666 8667 } else { 8668 return makeArray( [], extra ); 8669 } 8670 } 8671 8672 try { 8673 return makeArray( context.querySelectorAll(query), extra ); 8674 } catch(qsaError) {} 8675 8676 // qSA works strangely on Element-rooted queries 8677 // We can work around this by specifying an extra ID on the root 8678 // and working up from there (Thanks to Andrew Dupont for the technique) 8679 // IE 8 doesn't work on object elements 8680 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 8681 var oldContext = context, 8682 old = context.getAttribute( "id" ), 8683 nid = old || id, 8684 hasParent = context.parentNode, 8685 relativeHierarchySelector = /^\s*[+~]/.test( query ); 8686 8687 if ( !old ) { 8688 context.setAttribute( "id", nid ); 8689 } else { 8690 nid = nid.replace( /'/g, "\\$&" ); 8691 } 8692 if ( relativeHierarchySelector && hasParent ) { 8693 context = context.parentNode; 8694 } 8695 8696 try { 8697 if ( !relativeHierarchySelector || hasParent ) { 8698 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 8699 } 8700 8701 } catch(pseudoError) { 8702 } finally { 8703 if ( !old ) { 8704 oldContext.removeAttribute( "id" ); 8705 } 8706 } 8707 } 8708 } 8709 8710 return oldSizzle(query, context, extra, seed); 8711 }; 8712 8713 for ( var prop in oldSizzle ) { 8714 Sizzle[ prop ] = oldSizzle[ prop ]; 8715 } 8716 8717 // release memory in IE 8718 div = null; 8719 })(); 8720 } 8721 8722 (function(){ 8723 var html = document.documentElement, 8724 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 8725 8726 if ( matches ) { 8727 // Check to see if it's possible to do matchesSelector 8728 // on a disconnected node (IE 9 fails this) 8729 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 8730 pseudoWorks = false; 8731 8732 try { 8733 // This should fail with an exception 8734 // Gecko does not error, returns false instead 8735 matches.call( document.documentElement, "[test!='']:sizzle" ); 8736 8737 } catch( pseudoError ) { 8738 pseudoWorks = true; 8739 } 8740 8741 Sizzle.matchesSelector = function( node, expr ) { 8742 // Make sure that attribute selectors are quoted 8743 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 8744 8745 if ( !Sizzle.isXML( node ) ) { 8746 try { 8747 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 8748 var ret = matches.call( node, expr ); 8749 8750 // IE 9's matchesSelector returns false on disconnected nodes 8751 if ( ret || !disconnectedMatch || 8752 // As well, disconnected nodes are said to be in a document 8753 // fragment in IE 9, so check for that 8754 node.document && node.document.nodeType !== 11 ) { 8755 return ret; 8756 } 8757 } 8758 } catch(e) {} 8759 } 8760 8761 return Sizzle(expr, null, null, [node]).length > 0; 8762 }; 8763 } 8764 })(); 8765 8766 (function(){ 8767 var div = document.createElement("div"); 8768 8769 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 8770 8771 // Opera can't find a second classname (in 9.6) 8772 // Also, make sure that getElementsByClassName actually exists 8773 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 8774 return; 8775 } 8776 8777 // Safari caches class attributes, doesn't catch changes (in 3.2) 8778 div.lastChild.className = "e"; 8779 8780 if ( div.getElementsByClassName("e").length === 1 ) { 8781 return; 8782 } 8783 8784 Expr.order.splice(1, 0, "CLASS"); 8785 Expr.find.CLASS = function( match, context, isXML ) { 8786 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 8787 return context.getElementsByClassName(match[1]); 8788 } 8789 }; 8790 8791 // release memory in IE 8792 div = null; 8793 })(); 8794 8795 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8796 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8797 var elem = checkSet[i]; 8798 8799 if ( elem ) { 8800 var match = false; 8801 8802 elem = elem[dir]; 8803 8804 while ( elem ) { 8805 if ( elem[ expando ] === doneName ) { 8806 match = checkSet[elem.sizset]; 8807 break; 8808 } 8809 8810 if ( elem.nodeType === 1 && !isXML ){ 8811 elem[ expando ] = doneName; 8812 elem.sizset = i; 8813 } 8814 8815 if ( elem.nodeName.toLowerCase() === cur ) { 8816 match = elem; 8817 break; 8818 } 8819 8820 elem = elem[dir]; 8821 } 8822 8823 checkSet[i] = match; 8824 } 8825 } 8826 } 8827 8828 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 8829 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 8830 var elem = checkSet[i]; 8831 8832 if ( elem ) { 8833 var match = false; 8834 8835 elem = elem[dir]; 8836 8837 while ( elem ) { 8838 if ( elem[ expando ] === doneName ) { 8839 match = checkSet[elem.sizset]; 8840 break; 8841 } 8842 8843 if ( elem.nodeType === 1 ) { 8844 if ( !isXML ) { 8845 elem[ expando ] = doneName; 8846 elem.sizset = i; 8847 } 8848 8849 if ( typeof cur !== "string" ) { 8850 if ( elem === cur ) { 8851 match = true; 8852 break; 8853 } 8854 8855 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 8856 match = elem; 8857 break; 8858 } 8859 } 8860 8861 elem = elem[dir]; 8862 } 8863 8864 checkSet[i] = match; 8865 } 8866 } 8867 } 8868 8869 if ( document.documentElement.contains ) { 8870 Sizzle.contains = function( a, b ) { 8871 return a !== b && (a.contains ? a.contains(b) : true); 8872 }; 8873 8874 } else if ( document.documentElement.compareDocumentPosition ) { 8875 Sizzle.contains = function( a, b ) { 8876 return !!(a.compareDocumentPosition(b) & 16); 8877 }; 8878 8879 } else { 8880 Sizzle.contains = function() { 8881 return false; 8882 }; 8883 } 8884 8885 Sizzle.isXML = function( elem ) { 8886 // documentElement is verified for cases where it doesn't yet exist 8887 // (such as loading iframes in IE - #4833) 8888 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 8889 8890 return documentElement ? documentElement.nodeName !== "HTML" : false; 8891 }; 8892 8893 var posProcess = function( selector, context, seed ) { 8894 var match, 8895 tmpSet = [], 8896 later = "", 8897 root = context.nodeType ? [context] : context; 8898 8899 // Position selectors must be done after the filter 8900 // And so must :not(positional) so we move all PSEUDOs to the end 8901 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 8902 later += match[0]; 8903 selector = selector.replace( Expr.match.PSEUDO, "" ); 8904 } 8905 8906 selector = Expr.relative[selector] ? selector + "*" : selector; 8907 8908 for ( var i = 0, l = root.length; i < l; i++ ) { 8909 Sizzle( selector, root[i], tmpSet, seed ); 8910 } 8911 8912 return Sizzle.filter( later, tmpSet ); 8913 }; 8914 8915 // EXPOSE 8916 8917 window.tinymce.dom.Sizzle = Sizzle; 8918 8919 })(); 8920 8921 8922 (function(tinymce) { 8923 tinymce.dom.Element = function(id, settings) { 8924 var t = this, dom, el; 8925 8926 t.settings = settings = settings || {}; 8927 t.id = id; 8928 t.dom = dom = settings.dom || tinymce.DOM; 8929 8930 // Only IE leaks DOM references, this is a lot faster 8931 if (!tinymce.isIE) 8932 el = dom.get(t.id); 8933 8934 tinymce.each( 8935 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 8936 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 8937 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 8938 'isHidden,setHTML,get').split(/,/), function(k) { 8939 t[k] = function() { 8940 var a = [id], i; 8941 8942 for (i = 0; i < arguments.length; i++) 8943 a.push(arguments[i]); 8944 8945 a = dom[k].apply(dom, a); 8946 t.update(k); 8947 8948 return a; 8949 }; 8950 } 8951 ); 8952 8953 tinymce.extend(t, { 8954 on : function(n, f, s) { 8955 return tinymce.dom.Event.add(t.id, n, f, s); 8956 }, 8957 8958 getXY : function() { 8959 return { 8960 x : parseInt(t.getStyle('left')), 8961 y : parseInt(t.getStyle('top')) 8962 }; 8963 }, 8964 8965 getSize : function() { 8966 var n = dom.get(t.id); 8967 8968 return { 8969 w : parseInt(t.getStyle('width') || n.clientWidth), 8970 h : parseInt(t.getStyle('height') || n.clientHeight) 8971 }; 8972 }, 8973 8974 moveTo : function(x, y) { 8975 t.setStyles({left : x, top : y}); 8976 }, 8977 8978 moveBy : function(x, y) { 8979 var p = t.getXY(); 8980 8981 t.moveTo(p.x + x, p.y + y); 8982 }, 8983 8984 resizeTo : function(w, h) { 8985 t.setStyles({width : w, height : h}); 8986 }, 8987 8988 resizeBy : function(w, h) { 8989 var s = t.getSize(); 8990 8991 t.resizeTo(s.w + w, s.h + h); 8992 }, 8993 8994 update : function(k) { 8995 var b; 8996 8997 if (tinymce.isIE6 && settings.blocker) { 8998 k = k || ''; 8999 9000 // Ignore getters 9001 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) 9002 return; 9003 9004 // Remove blocker on remove 9005 if (k == 'remove') { 9006 dom.remove(t.blocker); 9007 return; 9008 } 9009 9010 if (!t.blocker) { 9011 t.blocker = dom.uniqueId(); 9012 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); 9013 dom.setStyle(b, 'opacity', 0); 9014 } else 9015 b = dom.get(t.blocker); 9016 9017 dom.setStyles(b, { 9018 left : t.getStyle('left', 1), 9019 top : t.getStyle('top', 1), 9020 width : t.getStyle('width', 1), 9021 height : t.getStyle('height', 1), 9022 display : t.getStyle('display', 1), 9023 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 9024 }); 9025 } 9026 } 9027 }); 9028 }; 9029 })(tinymce); 9030 9031 (function(tinymce) { 9032 function trimNl(s) { 9033 return s.replace(/[\n\r]+/g, ''); 9034 }; 9035 9036 // Shorten names 9037 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker; 9038 9039 tinymce.create('tinymce.dom.Selection', { 9040 Selection : function(dom, win, serializer, editor) { 9041 var t = this; 9042 9043 t.dom = dom; 9044 t.win = win; 9045 t.serializer = serializer; 9046 t.editor = editor; 9047 9048 // Add events 9049 each([ 9050 'onBeforeSetContent', 9051 9052 'onBeforeGetContent', 9053 9054 'onSetContent', 9055 9056 'onGetContent' 9057 ], function(e) { 9058 t[e] = new tinymce.util.Dispatcher(t); 9059 }); 9060 9061 // No W3C Range support 9062 if (!t.win.getSelection) 9063 t.tridentSel = new tinymce.dom.TridentSelection(t); 9064 9065 if (tinymce.isIE && dom.boxModel) 9066 this._fixIESelection(); 9067 9068 // Prevent leaks 9069 tinymce.addUnload(t.destroy, t); 9070 }, 9071 9072 setCursorLocation: function(node, offset) { 9073 var t = this; var r = t.dom.createRng(); 9074 r.setStart(node, offset); 9075 r.setEnd(node, offset); 9076 t.setRng(r); 9077 t.collapse(false); 9078 }, 9079 getContent : function(s) { 9080 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; 9081 9082 s = s || {}; 9083 wb = wa = ''; 9084 s.get = true; 9085 s.format = s.format || 'html'; 9086 s.forced_root_block = ''; 9087 t.onBeforeGetContent.dispatch(t, s); 9088 9089 if (s.format == 'text') 9090 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); 9091 9092 if (r.cloneContents) { 9093 n = r.cloneContents(); 9094 9095 if (n) 9096 e.appendChild(n); 9097 } else if (is(r.item) || is(r.htmlText)) { 9098 // IE will produce invalid markup if elements are present that 9099 // it doesn't understand like custom elements or HTML5 elements. 9100 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 9101 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText); 9102 e.removeChild(e.firstChild); 9103 } else 9104 e.innerHTML = r.toString(); 9105 9106 // Keep whitespace before and after 9107 if (/^\s/.test(e.innerHTML)) 9108 wb = ' '; 9109 9110 if (/\s+$/.test(e.innerHTML)) 9111 wa = ' '; 9112 9113 s.getInner = true; 9114 9115 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; 9116 t.onGetContent.dispatch(t, s); 9117 9118 return s.content; 9119 }, 9120 9121 setContent : function(content, args) { 9122 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 9123 9124 args = args || {format : 'html'}; 9125 args.set = true; 9126 content = args.content = content; 9127 9128 // Dispatch before set content event 9129 if (!args.no_events) 9130 self.onBeforeSetContent.dispatch(self, args); 9131 9132 content = args.content; 9133 9134 if (rng.insertNode) { 9135 // Make caret marker since insertNode places the caret in the beginning of text after insert 9136 content += '<span id="__caret">_</span>'; 9137 9138 // Delete and insert new node 9139 if (rng.startContainer == doc && rng.endContainer == doc) { 9140 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 9141 doc.body.innerHTML = content; 9142 } else { 9143 rng.deleteContents(); 9144 9145 if (doc.body.childNodes.length === 0) { 9146 doc.body.innerHTML = content; 9147 } else { 9148 // createContextualFragment doesn't exists in IE 9 DOMRanges 9149 if (rng.createContextualFragment) { 9150 rng.insertNode(rng.createContextualFragment(content)); 9151 } else { 9152 // Fake createContextualFragment call in IE 9 9153 frag = doc.createDocumentFragment(); 9154 temp = doc.createElement('div'); 9155 9156 frag.appendChild(temp); 9157 temp.outerHTML = content; 9158 9159 rng.insertNode(frag); 9160 } 9161 } 9162 } 9163 9164 // Move to caret marker 9165 caretNode = self.dom.get('__caret'); 9166 9167 // Make sure we wrap it compleatly, Opera fails with a simple select call 9168 rng = doc.createRange(); 9169 rng.setStartBefore(caretNode); 9170 rng.setEndBefore(caretNode); 9171 self.setRng(rng); 9172 9173 // Remove the caret position 9174 self.dom.remove('__caret'); 9175 9176 try { 9177 self.setRng(rng); 9178 } catch (ex) { 9179 // Might fail on Opera for some odd reason 9180 } 9181 } else { 9182 if (rng.item) { 9183 // Delete content and get caret text selection 9184 doc.execCommand('Delete', false, null); 9185 rng = self.getRng(); 9186 } 9187 9188 // Explorer removes spaces from the beginning of pasted contents 9189 if (/^\s+/.test(content)) { 9190 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 9191 self.dom.remove('__mce_tmp'); 9192 } else 9193 rng.pasteHTML(content); 9194 } 9195 9196 // Dispatch set content event 9197 if (!args.no_events) 9198 self.onSetContent.dispatch(self, args); 9199 }, 9200 9201 getStart : function() { 9202 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 9203 9204 if (rng.duplicate || rng.item) { 9205 // Control selection, return first item 9206 if (rng.item) 9207 return rng.item(0); 9208 9209 // Get start element 9210 checkRng = rng.duplicate(); 9211 checkRng.collapse(1); 9212 startElement = checkRng.parentElement(); 9213 if (startElement.ownerDocument !== self.dom.doc) { 9214 startElement = self.dom.getRoot(); 9215 } 9216 9217 // Check if range parent is inside the start element, then return the inner parent element 9218 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 9219 parentElement = node = rng.parentElement(); 9220 while (node = node.parentNode) { 9221 if (node == startElement) { 9222 startElement = parentElement; 9223 break; 9224 } 9225 } 9226 9227 return startElement; 9228 } else { 9229 startElement = rng.startContainer; 9230 9231 if (startElement.nodeType == 1 && startElement.hasChildNodes()) 9232 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 9233 9234 if (startElement && startElement.nodeType == 3) 9235 return startElement.parentNode; 9236 9237 return startElement; 9238 } 9239 }, 9240 9241 getEnd : function() { 9242 var self = this, rng = self.getRng(), endElement, endOffset; 9243 9244 if (rng.duplicate || rng.item) { 9245 if (rng.item) 9246 return rng.item(0); 9247 9248 rng = rng.duplicate(); 9249 rng.collapse(0); 9250 endElement = rng.parentElement(); 9251 if (endElement.ownerDocument !== self.dom.doc) { 9252 endElement = self.dom.getRoot(); 9253 } 9254 9255 if (endElement && endElement.nodeName == 'BODY') 9256 return endElement.lastChild || endElement; 9257 9258 return endElement; 9259 } else { 9260 endElement = rng.endContainer; 9261 endOffset = rng.endOffset; 9262 9263 if (endElement.nodeType == 1 && endElement.hasChildNodes()) 9264 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 9265 9266 if (endElement && endElement.nodeType == 3) 9267 return endElement.parentNode; 9268 9269 return endElement; 9270 } 9271 }, 9272 9273 getBookmark : function(type, normalized) { 9274 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; 9275 9276 function findIndex(name, element) { 9277 var index = 0; 9278 9279 each(dom.select(name), function(node, i) { 9280 if (node == element) 9281 index = i; 9282 }); 9283 9284 return index; 9285 }; 9286 9287 function normalizeTableCellSelection(rng) { 9288 function moveEndPoint(start) { 9289 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 9290 9291 container = rng[prefix + 'Container']; 9292 offset = rng[prefix + 'Offset']; 9293 9294 if (container.nodeType == 1 && container.nodeName == "TR") { 9295 childNodes = container.childNodes; 9296 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 9297 if (container) { 9298 offset = start ? 0 : container.childNodes.length; 9299 rng['set' + (start ? 'Start' : 'End')](container, offset); 9300 } 9301 } 9302 }; 9303 9304 moveEndPoint(true); 9305 moveEndPoint(); 9306 9307 return rng; 9308 }; 9309 9310 function getLocation() { 9311 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; 9312 9313 function getPoint(rng, start) { 9314 var container = rng[start ? 'startContainer' : 'endContainer'], 9315 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 9316 9317 if (container.nodeType == 3) { 9318 if (normalized) { 9319 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) 9320 offset += node.nodeValue.length; 9321 } 9322 9323 point.push(offset); 9324 } else { 9325 childNodes = container.childNodes; 9326 9327 if (offset >= childNodes.length && childNodes.length) { 9328 after = 1; 9329 offset = Math.max(0, childNodes.length - 1); 9330 } 9331 9332 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); 9333 } 9334 9335 for (; container && container != root; container = container.parentNode) 9336 point.push(t.dom.nodeIndex(container, normalized)); 9337 9338 return point; 9339 }; 9340 9341 bookmark.start = getPoint(rng, true); 9342 9343 if (!t.isCollapsed()) 9344 bookmark.end = getPoint(rng); 9345 9346 return bookmark; 9347 }; 9348 9349 if (type == 2) { 9350 if (t.tridentSel) 9351 return t.tridentSel.getBookmark(type); 9352 9353 return getLocation(); 9354 } 9355 9356 // Handle simple range 9357 if (type) 9358 return {rng : t.getRng()}; 9359 9360 rng = t.getRng(); 9361 id = dom.uniqueId(); 9362 collapsed = tinyMCE.activeEditor.selection.isCollapsed(); 9363 styles = 'overflow:hidden;line-height:0px'; 9364 9365 // Explorer method 9366 if (rng.duplicate || rng.item) { 9367 // Text selection 9368 if (!rng.item) { 9369 rng2 = rng.duplicate(); 9370 9371 try { 9372 // Insert start marker 9373 rng.collapse(); 9374 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 9375 9376 // Insert end marker 9377 if (!collapsed) { 9378 rng2.collapse(false); 9379 9380 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p> 9381 rng.moveToElementText(rng2.parentElement()); 9382 if (rng.compareEndPoints('StartToEnd', rng2) === 0) 9383 rng2.move('character', -1); 9384 9385 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 9386 } 9387 } catch (ex) { 9388 // IE might throw unspecified error so lets ignore it 9389 return null; 9390 } 9391 } else { 9392 // Control selection 9393 element = rng.item(0); 9394 name = element.nodeName; 9395 9396 return {name : name, index : findIndex(name, element)}; 9397 } 9398 } else { 9399 element = t.getNode(); 9400 name = element.nodeName; 9401 if (name == 'IMG') 9402 return {name : name, index : findIndex(name, element)}; 9403 9404 // W3C method 9405 rng2 = normalizeTableCellSelection(rng.cloneRange()); 9406 9407 // Insert end marker 9408 if (!collapsed) { 9409 rng2.collapse(false); 9410 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); 9411 } 9412 9413 rng = normalizeTableCellSelection(rng); 9414 rng.collapse(true); 9415 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); 9416 } 9417 9418 t.moveToBookmark({id : id, keep : 1}); 9419 9420 return {id : id}; 9421 }, 9422 9423 moveToBookmark : function(bookmark) { 9424 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; 9425 9426 function setEndPoint(start) { 9427 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 9428 9429 if (point) { 9430 offset = point[0]; 9431 9432 // Find container node 9433 for (node = root, i = point.length - 1; i >= 1; i--) { 9434 children = node.childNodes; 9435 9436 if (point[i] > children.length - 1) 9437 return; 9438 9439 node = children[point[i]]; 9440 } 9441 9442 // Move text offset to best suitable location 9443 if (node.nodeType === 3) 9444 offset = Math.min(point[0], node.nodeValue.length); 9445 9446 // Move element offset to best suitable location 9447 if (node.nodeType === 1) 9448 offset = Math.min(point[0], node.childNodes.length); 9449 9450 // Set offset within container node 9451 if (start) 9452 rng.setStart(node, offset); 9453 else 9454 rng.setEnd(node, offset); 9455 } 9456 9457 return true; 9458 }; 9459 9460 function restoreEndPoint(suffix) { 9461 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 9462 9463 if (marker) { 9464 node = marker.parentNode; 9465 9466 if (suffix == 'start') { 9467 if (!keep) { 9468 idx = dom.nodeIndex(marker); 9469 } else { 9470 node = marker.firstChild; 9471 idx = 1; 9472 } 9473 9474 startContainer = endContainer = node; 9475 startOffset = endOffset = idx; 9476 } else { 9477 if (!keep) { 9478 idx = dom.nodeIndex(marker); 9479 } else { 9480 node = marker.firstChild; 9481 idx = 1; 9482 } 9483 9484 endContainer = node; 9485 endOffset = idx; 9486 } 9487 9488 if (!keep) { 9489 prev = marker.previousSibling; 9490 next = marker.nextSibling; 9491 9492 // Remove all marker text nodes 9493 each(tinymce.grep(marker.childNodes), function(node) { 9494 if (node.nodeType == 3) 9495 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 9496 }); 9497 9498 // Remove marker but keep children if for example contents where inserted into the marker 9499 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature 9500 while (marker = dom.get(bookmark.id + '_' + suffix)) 9501 dom.remove(marker, 1); 9502 9503 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 9504 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact 9505 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { 9506 idx = prev.nodeValue.length; 9507 prev.appendData(next.nodeValue); 9508 dom.remove(next); 9509 9510 if (suffix == 'start') { 9511 startContainer = endContainer = prev; 9512 startOffset = endOffset = idx; 9513 } else { 9514 endContainer = prev; 9515 endOffset = idx; 9516 } 9517 } 9518 } 9519 } 9520 }; 9521 9522 function addBogus(node) { 9523 // Adds a bogus BR element for empty block elements 9524 if (dom.isBlock(node) && !node.innerHTML && !isIE) 9525 node.innerHTML = '<br data-mce-bogus="1" />'; 9526 9527 return node; 9528 }; 9529 9530 if (bookmark) { 9531 if (bookmark.start) { 9532 rng = dom.createRng(); 9533 root = dom.getRoot(); 9534 9535 if (t.tridentSel) 9536 return t.tridentSel.moveToBookmark(bookmark); 9537 9538 if (setEndPoint(true) && setEndPoint()) { 9539 t.setRng(rng); 9540 } 9541 } else if (bookmark.id) { 9542 // Restore start/end points 9543 restoreEndPoint('start'); 9544 restoreEndPoint('end'); 9545 9546 if (startContainer) { 9547 rng = dom.createRng(); 9548 rng.setStart(addBogus(startContainer), startOffset); 9549 rng.setEnd(addBogus(endContainer), endOffset); 9550 t.setRng(rng); 9551 } 9552 } else if (bookmark.name) { 9553 t.select(dom.select(bookmark.name)[bookmark.index]); 9554 } else if (bookmark.rng) 9555 t.setRng(bookmark.rng); 9556 } 9557 }, 9558 9559 select : function(node, content) { 9560 var t = this, dom = t.dom, rng = dom.createRng(), idx; 9561 9562 function setPoint(node, start) { 9563 var walker = new TreeWalker(node, node); 9564 9565 do { 9566 // Text node 9567 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) { 9568 if (start) 9569 rng.setStart(node, 0); 9570 else 9571 rng.setEnd(node, node.nodeValue.length); 9572 9573 return; 9574 } 9575 9576 // BR element 9577 if (node.nodeName == 'BR') { 9578 if (start) 9579 rng.setStartBefore(node); 9580 else 9581 rng.setEndBefore(node); 9582 9583 return; 9584 } 9585 } while (node = (start ? walker.next() : walker.prev())); 9586 }; 9587 9588 if (node) { 9589 idx = dom.nodeIndex(node); 9590 rng.setStart(node.parentNode, idx); 9591 rng.setEnd(node.parentNode, idx + 1); 9592 9593 // Find first/last text node or BR element 9594 if (content) { 9595 setPoint(node, 1); 9596 setPoint(node); 9597 } 9598 9599 t.setRng(rng); 9600 } 9601 9602 return node; 9603 }, 9604 9605 isCollapsed : function() { 9606 var t = this, r = t.getRng(), s = t.getSel(); 9607 9608 if (!r || r.item) 9609 return false; 9610 9611 if (r.compareEndPoints) 9612 return r.compareEndPoints('StartToEnd', r) === 0; 9613 9614 return !s || r.collapsed; 9615 }, 9616 9617 collapse : function(to_start) { 9618 var self = this, rng = self.getRng(), node; 9619 9620 // Control range on IE 9621 if (rng.item) { 9622 node = rng.item(0); 9623 rng = self.win.document.body.createTextRange(); 9624 rng.moveToElementText(node); 9625 } 9626 9627 rng.collapse(!!to_start); 9628 self.setRng(rng); 9629 }, 9630 9631 getSel : function() { 9632 var t = this, w = this.win; 9633 9634 return w.getSelection ? w.getSelection() : w.document.selection; 9635 }, 9636 9637 getRng : function(w3c) { 9638 var self = this, selection, rng, elm, doc = self.win.document; 9639 9640 // Found tridentSel object then we need to use that one 9641 if (w3c && self.tridentSel) { 9642 return self.tridentSel.getRangeAt(0); 9643 } 9644 9645 try { 9646 if (selection = self.getSel()) { 9647 rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange()); 9648 } 9649 } catch (ex) { 9650 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 9651 } 9652 9653 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 9654 if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) { 9655 elm = doc.selection.createRange().item(0); 9656 rng = doc.createRange(); 9657 rng.setStartBefore(elm); 9658 rng.setEndAfter(elm); 9659 } 9660 9661 // No range found then create an empty one 9662 // This can occur when the editor is placed in a hidden container element on Gecko 9663 // Or on IE when there was an exception 9664 if (!rng) { 9665 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 9666 } 9667 9668 // If range is at start of document then move it to start of body 9669 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 9670 elm = self.dom.getRoot(); 9671 rng.setStart(elm, 0); 9672 rng.setEnd(elm, 0); 9673 } 9674 9675 if (self.selectedRange && self.explicitRange) { 9676 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) { 9677 // Safari, Opera and Chrome only ever select text which causes the range to change. 9678 // This lets us use the originally set range if the selection hasn't been changed by the user. 9679 rng = self.explicitRange; 9680 } else { 9681 self.selectedRange = null; 9682 self.explicitRange = null; 9683 } 9684 } 9685 9686 return rng; 9687 }, 9688 9689 setRng : function(r, forward) { 9690 var s, t = this; 9691 9692 if (!t.tridentSel) { 9693 s = t.getSel(); 9694 9695 if (s) { 9696 t.explicitRange = r; 9697 9698 try { 9699 s.removeAllRanges(); 9700 } catch (ex) { 9701 // IE9 might throw errors here don't know why 9702 } 9703 9704 s.addRange(r); 9705 9706 // Forward is set to false and we have an extend function 9707 if (forward === false && s.extend) { 9708 s.collapse(r.endContainer, r.endOffset); 9709 s.extend(r.startContainer, r.startOffset); 9710 } 9711 9712 // adding range isn't always successful so we need to check range count otherwise an exception can occur 9713 t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null; 9714 } 9715 } else { 9716 // Is W3C Range 9717 if (r.cloneRange) { 9718 try { 9719 t.tridentSel.addRange(r); 9720 return; 9721 } catch (ex) { 9722 //IE9 throws an error here if called before selection is placed in the editor 9723 } 9724 } 9725 9726 // Is IE specific range 9727 try { 9728 r.select(); 9729 } catch (ex) { 9730 // Needed for some odd IE bug #1843306 9731 } 9732 } 9733 }, 9734 9735 setNode : function(n) { 9736 var t = this; 9737 9738 t.setContent(t.dom.getOuterHTML(n)); 9739 9740 return n; 9741 }, 9742 9743 getNode : function() { 9744 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; 9745 9746 function skipEmptyTextNodes(n, forwards) { 9747 var orig = n; 9748 while (n && n.nodeType === 3 && n.length === 0) { 9749 n = forwards ? n.nextSibling : n.previousSibling; 9750 } 9751 return n || orig; 9752 }; 9753 9754 // Range maybe lost after the editor is made visible again 9755 if (!rng) 9756 return t.dom.getRoot(); 9757 9758 if (rng.setStart) { 9759 elm = rng.commonAncestorContainer; 9760 9761 // Handle selection a image or other control like element such as anchors 9762 if (!rng.collapsed) { 9763 if (rng.startContainer == rng.endContainer) { 9764 if (rng.endOffset - rng.startOffset < 2) { 9765 if (rng.startContainer.hasChildNodes()) 9766 elm = rng.startContainer.childNodes[rng.startOffset]; 9767 } 9768 } 9769 9770 // If the anchor node is a element instead of a text node then return this element 9771 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 9772 // return sel.anchorNode.childNodes[sel.anchorOffset]; 9773 9774 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 9775 // This happens when you double click an underlined word in FireFox. 9776 if (start.nodeType === 3 && end.nodeType === 3) { 9777 if (start.length === rng.startOffset) { 9778 start = skipEmptyTextNodes(start.nextSibling, true); 9779 } else { 9780 start = start.parentNode; 9781 } 9782 if (rng.endOffset === 0) { 9783 end = skipEmptyTextNodes(end.previousSibling, false); 9784 } else { 9785 end = end.parentNode; 9786 } 9787 9788 if (start && start === end) 9789 return start; 9790 } 9791 } 9792 9793 if (elm && elm.nodeType == 3) 9794 return elm.parentNode; 9795 9796 return elm; 9797 } 9798 9799 return rng.item ? rng.item(0) : rng.parentElement(); 9800 }, 9801 9802 getSelectedBlocks : function(st, en) { 9803 var t = this, dom = t.dom, sb, eb, n, bl = []; 9804 9805 sb = dom.getParent(st || t.getStart(), dom.isBlock); 9806 eb = dom.getParent(en || t.getEnd(), dom.isBlock); 9807 9808 if (sb) 9809 bl.push(sb); 9810 9811 if (sb && eb && sb != eb) { 9812 n = sb; 9813 9814 var walker = new TreeWalker(sb, dom.getRoot()); 9815 while ((n = walker.next()) && n != eb) { 9816 if (dom.isBlock(n)) 9817 bl.push(n); 9818 } 9819 } 9820 9821 if (eb && sb != eb) 9822 bl.push(eb); 9823 9824 return bl; 9825 }, 9826 9827 isForward: function(){ 9828 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 9829 9830 // No support for selection direction then always return true 9831 if (!sel || sel.anchorNode == null || sel.focusNode == null) { 9832 return true; 9833 } 9834 9835 anchorRange = dom.createRng(); 9836 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 9837 anchorRange.collapse(true); 9838 9839 focusRange = dom.createRng(); 9840 focusRange.setStart(sel.focusNode, sel.focusOffset); 9841 focusRange.collapse(true); 9842 9843 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 9844 }, 9845 9846 normalize : function() { 9847 var self = this, rng, normalized, collapsed, node, sibling; 9848 9849 function normalizeEndPoint(start) { 9850 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName; 9851 9852 function hasBrBeforeAfter(node, left) { 9853 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 9854 9855 while (node = walker[left ? 'prev' : 'next']()) { 9856 if (node.nodeName === "BR") { 9857 return true; 9858 } 9859 } 9860 }; 9861 9862 // Walks the dom left/right to find a suitable text node to move the endpoint into 9863 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 9864 function findTextNodeRelative(left, startNode) { 9865 var walker, lastInlineElement; 9866 9867 startNode = startNode || container; 9868 walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body); 9869 9870 // Walk left until we hit a text node we can move to or a block/br/img 9871 while (node = walker[left ? 'prev' : 'next']()) { 9872 // Found text node that has a length 9873 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9874 container = node; 9875 offset = left ? node.nodeValue.length : 0; 9876 normalized = true; 9877 return; 9878 } 9879 9880 // Break if we find a block or a BR/IMG/INPUT etc 9881 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9882 return; 9883 } 9884 9885 lastInlineElement = node; 9886 } 9887 9888 // Only fetch the last inline element when in caret mode for now 9889 if (collapsed && lastInlineElement) { 9890 container = lastInlineElement; 9891 normalized = true; 9892 offset = 0; 9893 } 9894 }; 9895 9896 container = rng[(start ? 'start' : 'end') + 'Container']; 9897 offset = rng[(start ? 'start' : 'end') + 'Offset']; 9898 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 9899 9900 // If the container is a document move it to the body element 9901 if (container.nodeType === 9) { 9902 container = dom.getRoot(); 9903 offset = 0; 9904 } 9905 9906 // If the container is body try move it into the closest text node or position 9907 if (container === body) { 9908 // If start is before/after a image, table etc 9909 if (start) { 9910 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 9911 if (node) { 9912 nodeName = node.nodeName.toLowerCase(); 9913 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 9914 return; 9915 } 9916 } 9917 } 9918 9919 // Resolve the index 9920 if (container.hasChildNodes()) { 9921 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; 9922 offset = 0; 9923 9924 // Don't walk into elements that doesn't have any child nodes like a IMG 9925 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 9926 // Walk the DOM to find a text node to place the caret at or a BR 9927 node = container; 9928 walker = new TreeWalker(container, body); 9929 9930 do { 9931 // Found a text node use that position 9932 if (node.nodeType === 3 && node.nodeValue.length > 0) { 9933 offset = start ? 0 : node.nodeValue.length; 9934 container = node; 9935 normalized = true; 9936 break; 9937 } 9938 9939 // Found a BR/IMG element that we can place the caret before 9940 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 9941 offset = dom.nodeIndex(node); 9942 container = node.parentNode; 9943 9944 // Put caret after image when moving the end point 9945 if (node.nodeName == "IMG" && !start) { 9946 offset++; 9947 } 9948 9949 normalized = true; 9950 break; 9951 } 9952 } while (node = (start ? walker.next() : walker.prev())); 9953 } 9954 } 9955 } 9956 9957 // Lean the caret to the left if possible 9958 if (collapsed) { 9959 // So this: <b>x</b><i>|x</i> 9960 // Becomes: <b>x|</b><i>x</i> 9961 // Seems that only gecko has issues with this 9962 if (container.nodeType === 3 && offset === 0) { 9963 findTextNodeRelative(true); 9964 } 9965 9966 // Lean left into empty inline elements when the caret is before a BR 9967 // So this: <i><b></b><i>|<br></i> 9968 // Becomes: <i><b>|</b><i><br></i> 9969 // Seems that only gecko has issues with this 9970 if (container.nodeType === 1) { 9971 node = container.childNodes[offset]; 9972 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 9973 findTextNodeRelative(true, container.childNodes[offset]); 9974 } 9975 } 9976 } 9977 9978 // Lean the start of the selection right if possible 9979 // So this: x[<b>x]</b> 9980 // Becomes: x<b>[x]</b> 9981 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 9982 findTextNodeRelative(false); 9983 } 9984 9985 // Set endpoint if it was normalized 9986 if (normalized) 9987 rng['set' + (start ? 'Start' : 'End')](container, offset); 9988 }; 9989 9990 // Normalize only on non IE browsers for now 9991 if (tinymce.isIE) 9992 return; 9993 9994 rng = self.getRng(); 9995 collapsed = rng.collapsed; 9996 9997 // Normalize the end points 9998 normalizeEndPoint(true); 9999 10000 if (!collapsed) 10001 normalizeEndPoint(); 10002 10003 // Set the selection if it was normalized 10004 if (normalized) { 10005 // If it was collapsed then make sure it still is 10006 if (collapsed) { 10007 rng.collapse(true); 10008 } 10009 10010 //console.log(self.dom.dumpRng(rng)); 10011 self.setRng(rng, self.isForward()); 10012 } 10013 }, 10014 10015 selectorChanged: function(selector, callback) { 10016 var self = this, currentSelectors; 10017 10018 if (!self.selectorChangedData) { 10019 self.selectorChangedData = {}; 10020 currentSelectors = {}; 10021 10022 self.editor.onNodeChange.addToTop(function(ed, cm, node) { 10023 var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 10024 10025 // Check for new matching selectors 10026 each(self.selectorChangedData, function(callbacks, selector) { 10027 each(parents, function(node) { 10028 if (dom.is(node, selector)) { 10029 if (!currentSelectors[selector]) { 10030 // Execute callbacks 10031 each(callbacks, function(callback) { 10032 callback(true, {node: node, selector: selector, parents: parents}); 10033 }); 10034 10035 currentSelectors[selector] = callbacks; 10036 } 10037 10038 matchedSelectors[selector] = callbacks; 10039 return false; 10040 } 10041 }); 10042 }); 10043 10044 // Check if current selectors still match 10045 each(currentSelectors, function(callbacks, selector) { 10046 if (!matchedSelectors[selector]) { 10047 delete currentSelectors[selector]; 10048 10049 each(callbacks, function(callback) { 10050 callback(false, {node: node, selector: selector, parents: parents}); 10051 }); 10052 } 10053 }); 10054 }); 10055 } 10056 10057 // Add selector listeners 10058 if (!self.selectorChangedData[selector]) { 10059 self.selectorChangedData[selector] = []; 10060 } 10061 10062 self.selectorChangedData[selector].push(callback); 10063 10064 return self; 10065 }, 10066 10067 destroy : function(manual) { 10068 var self = this; 10069 10070 self.win = null; 10071 10072 // Manual destroy then remove unload handler 10073 if (!manual) 10074 tinymce.removeUnload(self.destroy); 10075 }, 10076 10077 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode 10078 _fixIESelection : function() { 10079 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; 10080 10081 // Return range from point or null if it failed 10082 function rngFromPoint(x, y) { 10083 var rng = body.createTextRange(); 10084 10085 try { 10086 rng.moveToPoint(x, y); 10087 } catch (ex) { 10088 // IE sometimes throws and exception, so lets just ignore it 10089 rng = null; 10090 } 10091 10092 return rng; 10093 }; 10094 10095 // Fires while the selection is changing 10096 function selectionChange(e) { 10097 var pointRng; 10098 10099 // Check if the button is down or not 10100 if (e.button) { 10101 // Create range from mouse position 10102 pointRng = rngFromPoint(e.x, e.y); 10103 10104 if (pointRng) { 10105 // Check if pointRange is before/after selection then change the endPoint 10106 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) 10107 pointRng.setEndPoint('StartToStart', startRng); 10108 else 10109 pointRng.setEndPoint('EndToEnd', startRng); 10110 10111 pointRng.select(); 10112 } 10113 } else 10114 endSelection(); 10115 } 10116 10117 // Removes listeners 10118 function endSelection() { 10119 var rng = doc.selection.createRange(); 10120 10121 // If the range is collapsed then use the last start range 10122 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) 10123 startRng.select(); 10124 10125 dom.unbind(doc, 'mouseup', endSelection); 10126 dom.unbind(doc, 'mousemove', selectionChange); 10127 startRng = started = 0; 10128 }; 10129 10130 // Make HTML element unselectable since we are going to handle selection by hand 10131 doc.documentElement.unselectable = true; 10132 10133 // Detect when user selects outside BODY 10134 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) { 10135 if (e.target.nodeName === 'HTML') { 10136 if (started) 10137 endSelection(); 10138 10139 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 10140 htmlElm = doc.documentElement; 10141 if (htmlElm.scrollHeight > htmlElm.clientHeight) 10142 return; 10143 10144 started = 1; 10145 // Setup start position 10146 startRng = rngFromPoint(e.x, e.y); 10147 if (startRng) { 10148 // Listen for selection change events 10149 dom.bind(doc, 'mouseup', endSelection); 10150 dom.bind(doc, 'mousemove', selectionChange); 10151 10152 dom.win.focus(); 10153 startRng.select(); 10154 } 10155 } 10156 }); 10157 } 10158 }); 10159 })(tinymce); 10160 10161 (function(tinymce) { 10162 tinymce.dom.Serializer = function(settings, dom, schema) { 10163 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; 10164 10165 // Support the old apply_source_formatting option 10166 if (!settings.apply_source_formatting) 10167 settings.indent = false; 10168 10169 // Default DOM and Schema if they are undefined 10170 dom = dom || tinymce.DOM; 10171 schema = schema || new tinymce.html.Schema(settings); 10172 settings.entity_encoding = settings.entity_encoding || 'named'; 10173 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 10174 10175 onPreProcess = new tinymce.util.Dispatcher(self); 10176 10177 onPostProcess = new tinymce.util.Dispatcher(self); 10178 10179 htmlParser = new tinymce.html.DomParser(settings, schema); 10180 10181 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 10182 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 10183 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 10184 10185 while (i--) { 10186 node = nodes[i]; 10187 10188 value = node.attributes.map[internalName]; 10189 if (value !== undef) { 10190 // Set external name to internal value and remove internal 10191 node.attr(name, value.length > 0 ? value : null); 10192 node.attr(internalName, null); 10193 } else { 10194 // No internal attribute found then convert the value we have in the DOM 10195 value = node.attributes.map[name]; 10196 10197 if (name === "style") 10198 value = dom.serializeStyle(dom.parseStyle(value), node.name); 10199 else if (urlConverter) 10200 value = urlConverter.call(urlConverterScope, value, name, node.name); 10201 10202 node.attr(name, value.length > 0 ? value : null); 10203 } 10204 } 10205 }); 10206 10207 // Remove internal classes mceItem<..> or mceSelected 10208 htmlParser.addAttributeFilter('class', function(nodes, name) { 10209 var i = nodes.length, node, value; 10210 10211 while (i--) { 10212 node = nodes[i]; 10213 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, ''); 10214 node.attr('class', value.length > 0 ? value : null); 10215 } 10216 }); 10217 10218 // Remove bookmark elements 10219 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 10220 var i = nodes.length, node; 10221 10222 while (i--) { 10223 node = nodes[i]; 10224 10225 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) 10226 node.remove(); 10227 } 10228 }); 10229 10230 // Remove expando attributes 10231 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) { 10232 var i = nodes.length; 10233 10234 while (i--) { 10235 nodes[i].attr(name, null); 10236 } 10237 }); 10238 10239 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 10240 htmlParser.addNodeFilter('script,style', function(nodes, name) { 10241 var i = nodes.length, node, value; 10242 10243 function trim(value) { 10244 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 10245 .replace(/^[\r\n]*|[\r\n]*$/g, '') 10246 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 10247 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 10248 }; 10249 10250 while (i--) { 10251 node = nodes[i]; 10252 value = node.firstChild ? node.firstChild.value : ''; 10253 10254 if (name === "script") { 10255 // Remove mce- prefix from script elements 10256 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); 10257 10258 if (value.length > 0) 10259 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 10260 } else { 10261 if (value.length > 0) 10262 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 10263 } 10264 } 10265 }); 10266 10267 // Convert comments to cdata and handle protected comments 10268 htmlParser.addNodeFilter('#comment', function(nodes, name) { 10269 var i = nodes.length, node; 10270 10271 while (i--) { 10272 node = nodes[i]; 10273 10274 if (node.value.indexOf('[CDATA[') === 0) { 10275 node.name = '#cdata'; 10276 node.type = 4; 10277 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 10278 } else if (node.value.indexOf('mce:protected ') === 0) { 10279 node.name = "#text"; 10280 node.type = 3; 10281 node.raw = true; 10282 node.value = unescape(node.value).substr(14); 10283 } 10284 } 10285 }); 10286 10287 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 10288 var i = nodes.length, node; 10289 10290 while (i--) { 10291 node = nodes[i]; 10292 if (node.type === 7) 10293 node.remove(); 10294 else if (node.type === 1) { 10295 if (name === "input" && !("type" in node.attributes.map)) 10296 node.attr('type', 'text'); 10297 } 10298 } 10299 }); 10300 10301 // Fix list elements, TODO: Replace this later 10302 if (settings.fix_list_elements) { 10303 htmlParser.addNodeFilter('ul,ol', function(nodes, name) { 10304 var i = nodes.length, node, parentNode; 10305 10306 while (i--) { 10307 node = nodes[i]; 10308 parentNode = node.parent; 10309 10310 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 10311 if (node.prev && node.prev.name === 'li') { 10312 node.prev.append(node); 10313 } 10314 } 10315 } 10316 }); 10317 } 10318 10319 // Remove internal data attributes 10320 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { 10321 var i = nodes.length; 10322 10323 while (i--) { 10324 nodes[i].attr(name, null); 10325 } 10326 }); 10327 10328 // Return public methods 10329 return { 10330 schema : schema, 10331 10332 addNodeFilter : htmlParser.addNodeFilter, 10333 10334 addAttributeFilter : htmlParser.addAttributeFilter, 10335 10336 onPreProcess : onPreProcess, 10337 10338 onPostProcess : onPostProcess, 10339 10340 serialize : function(node, args) { 10341 var impl, doc, oldDoc, htmlSerializer, content; 10342 10343 // Explorer won't clone contents of script and style and the 10344 // selected index of select elements are cleared on a clone operation. 10345 if (isIE && dom.select('script,style,select,map').length > 0) { 10346 content = node.innerHTML; 10347 node = node.cloneNode(false); 10348 dom.setHTML(node, content); 10349 } else 10350 node = node.cloneNode(true); 10351 10352 // Nodes needs to be attached to something in WebKit/Opera 10353 // Older builds of Opera crashes if you attach the node to an document created dynamically 10354 // and since we can't feature detect a crash we need to sniff the acutal build number 10355 // This fix will make DOM ranges and make Sizzle happy! 10356 impl = node.ownerDocument.implementation; 10357 if (impl.createHTMLDocument) { 10358 // Create an empty HTML document 10359 doc = impl.createHTMLDocument(""); 10360 10361 // Add the element or it's children if it's a body element to the new document 10362 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 10363 doc.body.appendChild(doc.importNode(node, true)); 10364 }); 10365 10366 // Grab first child or body element for serialization 10367 if (node.nodeName != 'BODY') 10368 node = doc.body.firstChild; 10369 else 10370 node = doc.body; 10371 10372 // set the new document in DOMUtils so createElement etc works 10373 oldDoc = dom.doc; 10374 dom.doc = doc; 10375 } 10376 10377 args = args || {}; 10378 args.format = args.format || 'html'; 10379 10380 // Pre process 10381 if (!args.no_events) { 10382 args.node = node; 10383 onPreProcess.dispatch(self, args); 10384 } 10385 10386 // Setup serializer 10387 htmlSerializer = new tinymce.html.Serializer(settings, schema); 10388 10389 // Parse and serialize HTML 10390 args.content = htmlSerializer.serialize( 10391 htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args) 10392 ); 10393 10394 // Replace all BOM characters for now until we can find a better solution 10395 if (!args.cleanup) 10396 args.content = args.content.replace(/\uFEFF|\u200B/g, ''); 10397 10398 // Post process 10399 if (!args.no_events) 10400 onPostProcess.dispatch(self, args); 10401 10402 // Restore the old document if it was changed 10403 if (oldDoc) 10404 dom.doc = oldDoc; 10405 10406 args.node = null; 10407 10408 return args.content; 10409 }, 10410 10411 addRules : function(rules) { 10412 schema.addValidElements(rules); 10413 }, 10414 10415 setRules : function(rules) { 10416 schema.setValidElements(rules); 10417 } 10418 }; 10419 }; 10420 })(tinymce); 10421 (function(tinymce) { 10422 tinymce.dom.ScriptLoader = function(settings) { 10423 var QUEUED = 0, 10424 LOADING = 1, 10425 LOADED = 2, 10426 states = {}, 10427 queue = [], 10428 scriptLoadedCallbacks = {}, 10429 queueLoadedCallbacks = [], 10430 loading = 0, 10431 undef; 10432 10433 function loadScript(url, callback) { 10434 var t = this, dom = tinymce.DOM, elm, uri, loc, id; 10435 10436 // Execute callback when script is loaded 10437 function done() { 10438 dom.remove(id); 10439 10440 if (elm) 10441 elm.onreadystatechange = elm.onload = elm = null; 10442 10443 callback(); 10444 }; 10445 10446 function error() { 10447 // Report the error so it's easier for people to spot loading errors 10448 if (typeof(console) !== "undefined" && console.log) 10449 console.log("Failed to load: " + url); 10450 10451 // We can't mark it as done if there is a load error since 10452 // A) We don't want to produce 404 errors on the server and 10453 // B) the onerror event won't fire on all browsers. 10454 // done(); 10455 }; 10456 10457 id = dom.uniqueId(); 10458 10459 if (tinymce.isIE6) { 10460 uri = new tinymce.util.URI(url); 10461 loc = location; 10462 10463 // If script is from same domain and we 10464 // use IE 6 then use XHR since it's more reliable 10465 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { 10466 tinymce.util.XHR.send({ 10467 url : tinymce._addVer(uri.getURI()), 10468 success : function(content) { 10469 // Create new temp script element 10470 var script = dom.create('script', { 10471 type : 'text/javascript' 10472 }); 10473 10474 // Evaluate script in global scope 10475 script.text = content; 10476 document.getElementsByTagName('head')[0].appendChild(script); 10477 dom.remove(script); 10478 10479 done(); 10480 }, 10481 10482 error : error 10483 }); 10484 10485 return; 10486 } 10487 } 10488 10489 // Create new script element 10490 elm = document.createElement('script'); 10491 elm.id = id; 10492 elm.type = 'text/javascript'; 10493 elm.src = tinymce._addVer(url); 10494 10495 // Add onload listener for non IE browsers since IE9 10496 // fires onload event before the script is parsed and executed 10497 if (!tinymce.isIE) 10498 elm.onload = done; 10499 10500 // Add onerror event will get fired on some browsers but not all of them 10501 elm.onerror = error; 10502 10503 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly 10504 if (!tinymce.isOpera) { 10505 elm.onreadystatechange = function() { 10506 var state = elm.readyState; 10507 10508 // Loaded state is passed on IE 6 however there 10509 // are known issues with this method but we can't use 10510 // XHR in a cross domain loading 10511 if (state == 'complete' || state == 'loaded') 10512 done(); 10513 }; 10514 } 10515 10516 // Most browsers support this feature so we report errors 10517 // for those at least to help users track their missing plugins etc 10518 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option 10519 /*elm.onerror = function() { 10520 alert('Failed to load: ' + url); 10521 };*/ 10522 10523 // Add script to document 10524 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 10525 }; 10526 10527 this.isDone = function(url) { 10528 return states[url] == LOADED; 10529 }; 10530 10531 this.markDone = function(url) { 10532 states[url] = LOADED; 10533 }; 10534 10535 this.add = this.load = function(url, callback, scope) { 10536 var item, state = states[url]; 10537 10538 // Add url to load queue 10539 if (state == undef) { 10540 queue.push(url); 10541 states[url] = QUEUED; 10542 } 10543 10544 if (callback) { 10545 // Store away callback for later execution 10546 if (!scriptLoadedCallbacks[url]) 10547 scriptLoadedCallbacks[url] = []; 10548 10549 scriptLoadedCallbacks[url].push({ 10550 func : callback, 10551 scope : scope || this 10552 }); 10553 } 10554 }; 10555 10556 this.loadQueue = function(callback, scope) { 10557 this.loadScripts(queue, callback, scope); 10558 }; 10559 10560 this.loadScripts = function(scripts, callback, scope) { 10561 var loadScripts; 10562 10563 function execScriptLoadedCallbacks(url) { 10564 // Execute URL callback functions 10565 tinymce.each(scriptLoadedCallbacks[url], function(callback) { 10566 callback.func.call(callback.scope); 10567 }); 10568 10569 scriptLoadedCallbacks[url] = undef; 10570 }; 10571 10572 queueLoadedCallbacks.push({ 10573 func : callback, 10574 scope : scope || this 10575 }); 10576 10577 loadScripts = function() { 10578 var loadingScripts = tinymce.grep(scripts); 10579 10580 // Current scripts has been handled 10581 scripts.length = 0; 10582 10583 // Load scripts that needs to be loaded 10584 tinymce.each(loadingScripts, function(url) { 10585 // Script is already loaded then execute script callbacks directly 10586 if (states[url] == LOADED) { 10587 execScriptLoadedCallbacks(url); 10588 return; 10589 } 10590 10591 // Is script not loading then start loading it 10592 if (states[url] != LOADING) { 10593 states[url] = LOADING; 10594 loading++; 10595 10596 loadScript(url, function() { 10597 states[url] = LOADED; 10598 loading--; 10599 10600 execScriptLoadedCallbacks(url); 10601 10602 // Load more scripts if they where added by the recently loaded script 10603 loadScripts(); 10604 }); 10605 } 10606 }); 10607 10608 // No scripts are currently loading then execute all pending queue loaded callbacks 10609 if (!loading) { 10610 tinymce.each(queueLoadedCallbacks, function(callback) { 10611 callback.func.call(callback.scope); 10612 }); 10613 10614 queueLoadedCallbacks.length = 0; 10615 } 10616 }; 10617 10618 loadScripts(); 10619 }; 10620 }; 10621 10622 // Global script loader 10623 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader(); 10624 })(tinymce); 10625 10626 (function(tinymce) { 10627 tinymce.dom.RangeUtils = function(dom) { 10628 var INVISIBLE_CHAR = '\uFEFF'; 10629 10630 this.walk = function(rng, callback) { 10631 var startContainer = rng.startContainer, 10632 startOffset = rng.startOffset, 10633 endContainer = rng.endContainer, 10634 endOffset = rng.endOffset, 10635 ancestor, startPoint, 10636 endPoint, node, parent, siblings, nodes; 10637 10638 // Handle table cell selection the table plugin enables 10639 // you to fake select table cells and perform formatting actions on them 10640 nodes = dom.select('td.mceSelected,th.mceSelected'); 10641 if (nodes.length > 0) { 10642 tinymce.each(nodes, function(node) { 10643 callback([node]); 10644 }); 10645 10646 return; 10647 } 10648 10649 function exclude(nodes) { 10650 var node; 10651 10652 // First node is excluded 10653 node = nodes[0]; 10654 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 10655 nodes.splice(0, 1); 10656 } 10657 10658 // Last node is excluded 10659 node = nodes[nodes.length - 1]; 10660 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 10661 nodes.splice(nodes.length - 1, 1); 10662 } 10663 10664 return nodes; 10665 }; 10666 10667 function collectSiblings(node, name, end_node) { 10668 var siblings = []; 10669 10670 for (; node && node != end_node; node = node[name]) 10671 siblings.push(node); 10672 10673 return siblings; 10674 }; 10675 10676 function findEndPoint(node, root) { 10677 do { 10678 if (node.parentNode == root) 10679 return node; 10680 10681 node = node.parentNode; 10682 } while(node); 10683 }; 10684 10685 function walkBoundary(start_node, end_node, next) { 10686 var siblingName = next ? 'nextSibling' : 'previousSibling'; 10687 10688 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 10689 parent = node.parentNode; 10690 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 10691 10692 if (siblings.length) { 10693 if (!next) 10694 siblings.reverse(); 10695 10696 callback(exclude(siblings)); 10697 } 10698 } 10699 }; 10700 10701 // If index based start position then resolve it 10702 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) 10703 startContainer = startContainer.childNodes[startOffset]; 10704 10705 // If index based end position then resolve it 10706 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) 10707 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; 10708 10709 // Same container 10710 if (startContainer == endContainer) 10711 return callback(exclude([startContainer])); 10712 10713 // Find common ancestor and end points 10714 ancestor = dom.findCommonAncestor(startContainer, endContainer); 10715 10716 // Process left side 10717 for (node = startContainer; node; node = node.parentNode) { 10718 if (node === endContainer) 10719 return walkBoundary(startContainer, ancestor, true); 10720 10721 if (node === ancestor) 10722 break; 10723 } 10724 10725 // Process right side 10726 for (node = endContainer; node; node = node.parentNode) { 10727 if (node === startContainer) 10728 return walkBoundary(endContainer, ancestor); 10729 10730 if (node === ancestor) 10731 break; 10732 } 10733 10734 // Find start/end point 10735 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 10736 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 10737 10738 // Walk left leaf 10739 walkBoundary(startContainer, startPoint, true); 10740 10741 // Walk the middle from start to end point 10742 siblings = collectSiblings( 10743 startPoint == startContainer ? startPoint : startPoint.nextSibling, 10744 'nextSibling', 10745 endPoint == endContainer ? endPoint.nextSibling : endPoint 10746 ); 10747 10748 if (siblings.length) 10749 callback(exclude(siblings)); 10750 10751 // Walk right leaf 10752 walkBoundary(endContainer, endPoint); 10753 }; 10754 10755 this.split = function(rng) { 10756 var startContainer = rng.startContainer, 10757 startOffset = rng.startOffset, 10758 endContainer = rng.endContainer, 10759 endOffset = rng.endOffset; 10760 10761 function splitText(node, offset) { 10762 return node.splitText(offset); 10763 }; 10764 10765 // Handle single text node 10766 if (startContainer == endContainer && startContainer.nodeType == 3) { 10767 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10768 endContainer = splitText(startContainer, startOffset); 10769 startContainer = endContainer.previousSibling; 10770 10771 if (endOffset > startOffset) { 10772 endOffset = endOffset - startOffset; 10773 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 10774 endOffset = endContainer.nodeValue.length; 10775 startOffset = 0; 10776 } else { 10777 endOffset = 0; 10778 } 10779 } 10780 } else { 10781 // Split startContainer text node if needed 10782 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 10783 startContainer = splitText(startContainer, startOffset); 10784 startOffset = 0; 10785 } 10786 10787 // Split endContainer text node if needed 10788 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 10789 endContainer = splitText(endContainer, endOffset).previousSibling; 10790 endOffset = endContainer.nodeValue.length; 10791 } 10792 } 10793 10794 return { 10795 startContainer : startContainer, 10796 startOffset : startOffset, 10797 endContainer : endContainer, 10798 endOffset : endOffset 10799 }; 10800 }; 10801 10802 }; 10803 10804 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) { 10805 if (rng1 && rng2) { 10806 // Compare native IE ranges 10807 if (rng1.item || rng1.duplicate) { 10808 // Both are control ranges and the selected element matches 10809 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) 10810 return true; 10811 10812 // Both are text ranges and the range matches 10813 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) 10814 return true; 10815 } else { 10816 // Compare w3c ranges 10817 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 10818 } 10819 } 10820 10821 return false; 10822 }; 10823 })(tinymce); 10824 10825 (function(tinymce) { 10826 var Event = tinymce.dom.Event, each = tinymce.each; 10827 10828 tinymce.create('tinymce.ui.KeyboardNavigation', { 10829 KeyboardNavigation: function(settings, dom) { 10830 var t = this, root = settings.root, items = settings.items, 10831 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, 10832 excludeFromTabOrder = settings.excludeFromTabOrder, 10833 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; 10834 10835 dom = dom || tinymce.DOM; 10836 10837 itemFocussed = function(evt) { 10838 focussedId = evt.target.id; 10839 }; 10840 10841 itemBlurred = function(evt) { 10842 dom.setAttrib(evt.target.id, 'tabindex', '-1'); 10843 }; 10844 10845 rootFocussed = function(evt) { 10846 var item = dom.get(focussedId); 10847 dom.setAttrib(item, 'tabindex', '0'); 10848 item.focus(); 10849 }; 10850 10851 t.focus = function() { 10852 dom.get(focussedId).focus(); 10853 }; 10854 10855 t.destroy = function() { 10856 each(items, function(item) { 10857 var elm = dom.get(item.id); 10858 10859 dom.unbind(elm, 'focus', itemFocussed); 10860 dom.unbind(elm, 'blur', itemBlurred); 10861 }); 10862 10863 var rootElm = dom.get(root); 10864 dom.unbind(rootElm, 'focus', rootFocussed); 10865 dom.unbind(rootElm, 'keydown', rootKeydown); 10866 10867 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; 10868 t.destroy = function() {}; 10869 }; 10870 10871 t.moveFocus = function(dir, evt) { 10872 var idx = -1, controls = t.controls, newFocus; 10873 10874 if (!focussedId) 10875 return; 10876 10877 each(items, function(item, index) { 10878 if (item.id === focussedId) { 10879 idx = index; 10880 return false; 10881 } 10882 }); 10883 10884 idx += dir; 10885 if (idx < 0) { 10886 idx = items.length - 1; 10887 } else if (idx >= items.length) { 10888 idx = 0; 10889 } 10890 10891 newFocus = items[idx]; 10892 dom.setAttrib(focussedId, 'tabindex', '-1'); 10893 dom.setAttrib(newFocus.id, 'tabindex', '0'); 10894 dom.get(newFocus.id).focus(); 10895 10896 if (settings.actOnFocus) { 10897 settings.onAction(newFocus.id); 10898 } 10899 10900 if (evt) 10901 Event.cancel(evt); 10902 }; 10903 10904 rootKeydown = function(evt) { 10905 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; 10906 10907 switch (evt.keyCode) { 10908 case DOM_VK_LEFT: 10909 if (enableLeftRight) t.moveFocus(-1); 10910 break; 10911 10912 case DOM_VK_RIGHT: 10913 if (enableLeftRight) t.moveFocus(1); 10914 break; 10915 10916 case DOM_VK_UP: 10917 if (enableUpDown) t.moveFocus(-1); 10918 break; 10919 10920 case DOM_VK_DOWN: 10921 if (enableUpDown) t.moveFocus(1); 10922 break; 10923 10924 case DOM_VK_ESCAPE: 10925 if (settings.onCancel) { 10926 settings.onCancel(); 10927 Event.cancel(evt); 10928 } 10929 break; 10930 10931 case DOM_VK_ENTER: 10932 case DOM_VK_RETURN: 10933 case DOM_VK_SPACE: 10934 if (settings.onAction) { 10935 settings.onAction(focussedId); 10936 Event.cancel(evt); 10937 } 10938 break; 10939 } 10940 }; 10941 10942 // Set up state and listeners for each item. 10943 each(items, function(item, idx) { 10944 var tabindex, elm; 10945 10946 if (!item.id) { 10947 item.id = dom.uniqueId('_mce_item_'); 10948 } 10949 10950 elm = dom.get(item.id); 10951 10952 if (excludeFromTabOrder) { 10953 dom.bind(elm, 'blur', itemBlurred); 10954 tabindex = '-1'; 10955 } else { 10956 tabindex = (idx === 0 ? '0' : '-1'); 10957 } 10958 10959 elm.setAttribute('tabindex', tabindex); 10960 dom.bind(elm, 'focus', itemFocussed); 10961 }); 10962 10963 // Setup initial state for root element. 10964 if (items[0]){ 10965 focussedId = items[0].id; 10966 } 10967 10968 dom.setAttrib(root, 'tabindex', '-1'); 10969 10970 // Setup listeners for root element. 10971 var rootElm = dom.get(root); 10972 dom.bind(rootElm, 'focus', rootFocussed); 10973 dom.bind(rootElm, 'keydown', rootKeydown); 10974 } 10975 }); 10976 })(tinymce); 10977 10978 (function(tinymce) { 10979 // Shorten class names 10980 var DOM = tinymce.DOM, is = tinymce.is; 10981 10982 tinymce.create('tinymce.ui.Control', { 10983 Control : function(id, s, editor) { 10984 this.id = id; 10985 this.settings = s = s || {}; 10986 this.rendered = false; 10987 this.onRender = new tinymce.util.Dispatcher(this); 10988 this.classPrefix = ''; 10989 this.scope = s.scope || this; 10990 this.disabled = 0; 10991 this.active = 0; 10992 this.editor = editor; 10993 }, 10994 10995 setAriaProperty : function(property, value) { 10996 var element = DOM.get(this.id + '_aria') || DOM.get(this.id); 10997 if (element) { 10998 DOM.setAttrib(element, 'aria-' + property, !!value); 10999 } 11000 }, 11001 11002 focus : function() { 11003 DOM.get(this.id).focus(); 11004 }, 11005 11006 setDisabled : function(s) { 11007 if (s != this.disabled) { 11008 this.setAriaProperty('disabled', s); 11009 11010 this.setState('Disabled', s); 11011 this.setState('Enabled', !s); 11012 this.disabled = s; 11013 } 11014 }, 11015 11016 isDisabled : function() { 11017 return this.disabled; 11018 }, 11019 11020 setActive : function(s) { 11021 if (s != this.active) { 11022 this.setState('Active', s); 11023 this.active = s; 11024 this.setAriaProperty('pressed', s); 11025 } 11026 }, 11027 11028 isActive : function() { 11029 return this.active; 11030 }, 11031 11032 setState : function(c, s) { 11033 var n = DOM.get(this.id); 11034 11035 c = this.classPrefix + c; 11036 11037 if (s) 11038 DOM.addClass(n, c); 11039 else 11040 DOM.removeClass(n, c); 11041 }, 11042 11043 isRendered : function() { 11044 return this.rendered; 11045 }, 11046 11047 renderHTML : function() { 11048 }, 11049 11050 renderTo : function(n) { 11051 DOM.setHTML(n, this.renderHTML()); 11052 }, 11053 11054 postRender : function() { 11055 var t = this, b; 11056 11057 // Set pending states 11058 if (is(t.disabled)) { 11059 b = t.disabled; 11060 t.disabled = -1; 11061 t.setDisabled(b); 11062 } 11063 11064 if (is(t.active)) { 11065 b = t.active; 11066 t.active = -1; 11067 t.setActive(b); 11068 } 11069 }, 11070 11071 remove : function() { 11072 DOM.remove(this.id); 11073 this.destroy(); 11074 }, 11075 11076 destroy : function() { 11077 tinymce.dom.Event.clear(this.id); 11078 } 11079 }); 11080 })(tinymce); 11081 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { 11082 Container : function(id, s, editor) { 11083 this.parent(id, s, editor); 11084 11085 this.controls = []; 11086 11087 this.lookup = {}; 11088 }, 11089 11090 add : function(c) { 11091 this.lookup[c.id] = c; 11092 this.controls.push(c); 11093 11094 return c; 11095 }, 11096 11097 get : function(n) { 11098 return this.lookup[n]; 11099 } 11100 }); 11101 11102 11103 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 11104 Separator : function(id, s) { 11105 this.parent(id, s); 11106 this.classPrefix = 'mceSeparator'; 11107 this.setDisabled(true); 11108 }, 11109 11110 renderHTML : function() { 11111 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); 11112 } 11113 }); 11114 11115 (function(tinymce) { 11116 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11117 11118 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', { 11119 MenuItem : function(id, s) { 11120 this.parent(id, s); 11121 this.classPrefix = 'mceMenuItem'; 11122 }, 11123 11124 setSelected : function(s) { 11125 this.setState('Selected', s); 11126 this.setAriaProperty('checked', !!s); 11127 this.selected = s; 11128 }, 11129 11130 isSelected : function() { 11131 return this.selected; 11132 }, 11133 11134 postRender : function() { 11135 var t = this; 11136 11137 t.parent(); 11138 11139 // Set pending state 11140 if (is(t.selected)) 11141 t.setSelected(t.selected); 11142 } 11143 }); 11144 })(tinymce); 11145 11146 (function(tinymce) { 11147 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk; 11148 11149 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', { 11150 Menu : function(id, s) { 11151 var t = this; 11152 11153 t.parent(id, s); 11154 t.items = {}; 11155 t.collapsed = false; 11156 t.menuCount = 0; 11157 t.onAddItem = new tinymce.util.Dispatcher(this); 11158 }, 11159 11160 expand : function(d) { 11161 var t = this; 11162 11163 if (d) { 11164 walk(t, function(o) { 11165 if (o.expand) 11166 o.expand(); 11167 }, 'items', t); 11168 } 11169 11170 t.collapsed = false; 11171 }, 11172 11173 collapse : function(d) { 11174 var t = this; 11175 11176 if (d) { 11177 walk(t, function(o) { 11178 if (o.collapse) 11179 o.collapse(); 11180 }, 'items', t); 11181 } 11182 11183 t.collapsed = true; 11184 }, 11185 11186 isCollapsed : function() { 11187 return this.collapsed; 11188 }, 11189 11190 add : function(o) { 11191 if (!o.settings) 11192 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o); 11193 11194 this.onAddItem.dispatch(this, o); 11195 11196 return this.items[o.id] = o; 11197 }, 11198 11199 addSeparator : function() { 11200 return this.add({separator : true}); 11201 }, 11202 11203 addMenu : function(o) { 11204 if (!o.collapse) 11205 o = this.createMenu(o); 11206 11207 this.menuCount++; 11208 11209 return this.add(o); 11210 }, 11211 11212 hasMenus : function() { 11213 return this.menuCount !== 0; 11214 }, 11215 11216 remove : function(o) { 11217 delete this.items[o.id]; 11218 }, 11219 11220 removeAll : function() { 11221 var t = this; 11222 11223 walk(t, function(o) { 11224 if (o.removeAll) 11225 o.removeAll(); 11226 else 11227 o.remove(); 11228 11229 o.destroy(); 11230 }, 'items', t); 11231 11232 t.items = {}; 11233 }, 11234 11235 createMenu : function(o) { 11236 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o); 11237 11238 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem); 11239 11240 return m; 11241 } 11242 }); 11243 })(tinymce); 11244 (function(tinymce) { 11245 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element; 11246 11247 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { 11248 DropMenu : function(id, s) { 11249 s = s || {}; 11250 s.container = s.container || DOM.doc.body; 11251 s.offset_x = s.offset_x || 0; 11252 s.offset_y = s.offset_y || 0; 11253 s.vp_offset_x = s.vp_offset_x || 0; 11254 s.vp_offset_y = s.vp_offset_y || 0; 11255 11256 if (is(s.icons) && !s.icons) 11257 s['class'] += ' mceNoIcons'; 11258 11259 this.parent(id, s); 11260 this.onShowMenu = new tinymce.util.Dispatcher(this); 11261 this.onHideMenu = new tinymce.util.Dispatcher(this); 11262 this.classPrefix = 'mceMenu'; 11263 }, 11264 11265 createMenu : function(s) { 11266 var t = this, cs = t.settings, m; 11267 11268 s.container = s.container || cs.container; 11269 s.parent = t; 11270 s.constrain = s.constrain || cs.constrain; 11271 s['class'] = s['class'] || cs['class']; 11272 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; 11273 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; 11274 s.keyboard_focus = cs.keyboard_focus; 11275 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); 11276 11277 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); 11278 11279 return m; 11280 }, 11281 11282 focus : function() { 11283 var t = this; 11284 if (t.keyboardNav) { 11285 t.keyboardNav.focus(); 11286 } 11287 }, 11288 11289 update : function() { 11290 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; 11291 11292 tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth; 11293 th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight; 11294 11295 if (!DOM.boxModel) 11296 t.element.setStyles({width : tw + 2, height : th + 2}); 11297 else 11298 t.element.setStyles({width : tw, height : th}); 11299 11300 if (s.max_width) 11301 DOM.setStyle(co, 'width', tw); 11302 11303 if (s.max_height) { 11304 DOM.setStyle(co, 'height', th); 11305 11306 if (tb.clientHeight < s.max_height) 11307 DOM.setStyle(co, 'overflow', 'hidden'); 11308 } 11309 }, 11310 11311 showMenu : function(x, y, px) { 11312 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; 11313 11314 t.collapse(1); 11315 11316 if (t.isMenuVisible) 11317 return; 11318 11319 if (!t.rendered) { 11320 co = DOM.add(t.settings.container, t.renderNode()); 11321 11322 each(t.items, function(o) { 11323 o.postRender(); 11324 }); 11325 11326 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11327 } else 11328 co = DOM.get('menu_' + t.id); 11329 11330 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug 11331 if (!tinymce.isOpera) 11332 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF}); 11333 11334 DOM.show(co); 11335 t.update(); 11336 11337 x += s.offset_x || 0; 11338 y += s.offset_y || 0; 11339 vp.w -= 4; 11340 vp.h -= 4; 11341 11342 // Move inside viewport if not submenu 11343 if (s.constrain) { 11344 w = co.clientWidth - ot; 11345 h = co.clientHeight - ot; 11346 mx = vp.x + vp.w; 11347 my = vp.y + vp.h; 11348 11349 if ((x + s.vp_offset_x + w) > mx) 11350 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w); 11351 11352 if ((y + s.vp_offset_y + h) > my) 11353 y = Math.max(0, (my - s.vp_offset_y) - h); 11354 } 11355 11356 DOM.setStyles(co, {left : x , top : y}); 11357 t.element.update(); 11358 11359 t.isMenuVisible = 1; 11360 t.mouseClickFunc = Event.add(co, 'click', function(e) { 11361 var m; 11362 11363 e = e.target; 11364 11365 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) { 11366 m = t.items[e.id]; 11367 11368 if (m.isDisabled()) 11369 return; 11370 11371 dm = t; 11372 11373 while (dm) { 11374 if (dm.hideMenu) 11375 dm.hideMenu(); 11376 11377 dm = dm.settings.parent; 11378 } 11379 11380 if (m.settings.onclick) 11381 m.settings.onclick(e); 11382 11383 return false; // Cancel to fix onbeforeunload problem 11384 } 11385 }); 11386 11387 if (t.hasMenus()) { 11388 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) { 11389 var m, r, mi; 11390 11391 e = e.target; 11392 if (e && (e = DOM.getParent(e, 'tr'))) { 11393 m = t.items[e.id]; 11394 11395 if (t.lastMenu) 11396 t.lastMenu.collapse(1); 11397 11398 if (m.isDisabled()) 11399 return; 11400 11401 if (e && DOM.hasClass(e, cp + 'ItemSub')) { 11402 //p = DOM.getPos(s.container); 11403 r = DOM.getRect(e); 11404 m.showMenu((r.x + r.w - ot), r.y - ot, r.x); 11405 t.lastMenu = m; 11406 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); 11407 } 11408 } 11409 }); 11410 } 11411 11412 Event.add(co, 'keydown', t._keyHandler, t); 11413 11414 t.onShowMenu.dispatch(t); 11415 11416 if (s.keyboard_focus) { 11417 t._setupKeyboardNav(); 11418 } 11419 }, 11420 11421 hideMenu : function(c) { 11422 var t = this, co = DOM.get('menu_' + t.id), e; 11423 11424 if (!t.isMenuVisible) 11425 return; 11426 11427 if (t.keyboardNav) t.keyboardNav.destroy(); 11428 Event.remove(co, 'mouseover', t.mouseOverFunc); 11429 Event.remove(co, 'click', t.mouseClickFunc); 11430 Event.remove(co, 'keydown', t._keyHandler); 11431 DOM.hide(co); 11432 t.isMenuVisible = 0; 11433 11434 if (!c) 11435 t.collapse(1); 11436 11437 if (t.element) 11438 t.element.hide(); 11439 11440 if (e = DOM.get(t.id)) 11441 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); 11442 11443 t.onHideMenu.dispatch(t); 11444 }, 11445 11446 add : function(o) { 11447 var t = this, co; 11448 11449 o = t.parent(o); 11450 11451 if (t.isRendered && (co = DOM.get('menu_' + t.id))) 11452 t._add(DOM.select('tbody', co)[0], o); 11453 11454 return o; 11455 }, 11456 11457 collapse : function(d) { 11458 this.parent(d); 11459 this.hideMenu(1); 11460 }, 11461 11462 remove : function(o) { 11463 DOM.remove(o.id); 11464 this.destroy(); 11465 11466 return this.parent(o); 11467 }, 11468 11469 destroy : function() { 11470 var t = this, co = DOM.get('menu_' + t.id); 11471 11472 if (t.keyboardNav) t.keyboardNav.destroy(); 11473 Event.remove(co, 'mouseover', t.mouseOverFunc); 11474 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); 11475 Event.remove(co, 'click', t.mouseClickFunc); 11476 Event.remove(co, 'keydown', t._keyHandler); 11477 11478 if (t.element) 11479 t.element.remove(); 11480 11481 DOM.remove(co); 11482 }, 11483 11484 renderNode : function() { 11485 var t = this, s = t.settings, n, tb, co, w; 11486 11487 w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); 11488 if (t.settings.parent) { 11489 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); 11490 } 11491 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); 11492 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); 11493 11494 if (s.menu_line) 11495 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); 11496 11497 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); 11498 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); 11499 tb = DOM.add(n, 'tbody'); 11500 11501 each(t.items, function(o) { 11502 t._add(tb, o); 11503 }); 11504 11505 t.rendered = true; 11506 11507 return w; 11508 }, 11509 11510 // Internal functions 11511 _setupKeyboardNav : function(){ 11512 var contextMenu, menuItems, t=this; 11513 contextMenu = DOM.get('menu_' + t.id); 11514 menuItems = DOM.select('a[role=option]', 'menu_' + t.id); 11515 menuItems.splice(0,0,contextMenu); 11516 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 11517 root: 'menu_' + t.id, 11518 items: menuItems, 11519 onCancel: function() { 11520 t.hideMenu(); 11521 }, 11522 enableUpDown: true 11523 }); 11524 contextMenu.focus(); 11525 }, 11526 11527 _keyHandler : function(evt) { 11528 var t = this, e; 11529 switch (evt.keyCode) { 11530 case 37: // Left 11531 if (t.settings.parent) { 11532 t.hideMenu(); 11533 t.settings.parent.focus(); 11534 Event.cancel(evt); 11535 } 11536 break; 11537 case 39: // Right 11538 if (t.mouseOverFunc) 11539 t.mouseOverFunc(evt); 11540 break; 11541 } 11542 }, 11543 11544 _add : function(tb, o) { 11545 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic; 11546 11547 if (s.separator) { 11548 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); 11549 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); 11550 11551 if (n = ro.previousSibling) 11552 DOM.addClass(n, 'mceLast'); 11553 11554 return; 11555 } 11556 11557 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); 11558 n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); 11559 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); 11560 11561 if (s.parent) { 11562 DOM.setAttrib(a, 'aria-haspopup', 'true'); 11563 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); 11564 } 11565 11566 DOM.addClass(it, s['class']); 11567 // n = DOM.add(n, 'span', {'class' : 'item'}); 11568 11569 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')}); 11570 11571 if (s.icon_src) 11572 DOM.add(ic, 'img', {src : s.icon_src}); 11573 11574 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title); 11575 11576 if (o.settings.style) { 11577 if (typeof o.settings.style == "function") 11578 o.settings.style = o.settings.style(); 11579 11580 DOM.setAttrib(n, 'style', o.settings.style); 11581 } 11582 11583 if (tb.childNodes.length == 1) 11584 DOM.addClass(ro, 'mceFirst'); 11585 11586 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) 11587 DOM.addClass(ro, 'mceFirst'); 11588 11589 if (o.collapse) 11590 DOM.addClass(ro, cp + 'ItemSub'); 11591 11592 if (n = ro.previousSibling) 11593 DOM.removeClass(n, 'mceLast'); 11594 11595 DOM.addClass(ro, 'mceLast'); 11596 } 11597 }); 11598 })(tinymce); 11599 (function(tinymce) { 11600 var DOM = tinymce.DOM; 11601 11602 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { 11603 Button : function(id, s, ed) { 11604 this.parent(id, s, ed); 11605 this.classPrefix = 'mceButton'; 11606 }, 11607 11608 renderHTML : function() { 11609 var cp = this.classPrefix, s = this.settings, h, l; 11610 11611 l = DOM.encode(s.label || ''); 11612 h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">'; 11613 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) 11614 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11615 else 11616 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : ''); 11617 11618 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 11619 h += '</a>'; 11620 return h; 11621 }, 11622 11623 postRender : function() { 11624 var t = this, s = t.settings, imgBookmark; 11625 11626 // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so 11627 // need to keep the selection in case the selection is lost 11628 if (tinymce.isIE && t.editor) { 11629 tinymce.dom.Event.add(t.id, 'mousedown', function(e) { 11630 var nodeName = t.editor.selection.getNode().nodeName; 11631 imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null; 11632 }); 11633 } 11634 tinymce.dom.Event.add(t.id, 'click', function(e) { 11635 if (!t.isDisabled()) { 11636 // restore the selection in case the selection is lost in IE 11637 if (tinymce.isIE && t.editor && imgBookmark !== null) { 11638 t.editor.selection.moveToBookmark(imgBookmark); 11639 } 11640 return s.onclick.call(s.scope, e); 11641 } 11642 }); 11643 tinymce.dom.Event.add(t.id, 'keyup', function(e) { 11644 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) 11645 return s.onclick.call(s.scope, e); 11646 }); 11647 } 11648 }); 11649 })(tinymce); 11650 11651 (function(tinymce) { 11652 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11653 11654 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { 11655 ListBox : function(id, s, ed) { 11656 var t = this; 11657 11658 t.parent(id, s, ed); 11659 11660 t.items = []; 11661 11662 t.onChange = new Dispatcher(t); 11663 11664 t.onPostRender = new Dispatcher(t); 11665 11666 t.onAdd = new Dispatcher(t); 11667 11668 t.onRenderMenu = new tinymce.util.Dispatcher(this); 11669 11670 t.classPrefix = 'mceListBox'; 11671 t.marked = {}; 11672 }, 11673 11674 select : function(va) { 11675 var t = this, fv, f; 11676 11677 t.marked = {}; 11678 11679 if (va == undef) 11680 return t.selectByIndex(-1); 11681 11682 // Is string or number make function selector 11683 if (va && typeof(va)=="function") 11684 f = va; 11685 else { 11686 f = function(v) { 11687 return v == va; 11688 }; 11689 } 11690 11691 // Do we need to do something? 11692 if (va != t.selectedValue) { 11693 // Find item 11694 each(t.items, function(o, i) { 11695 if (f(o.value)) { 11696 fv = 1; 11697 t.selectByIndex(i); 11698 return false; 11699 } 11700 }); 11701 11702 if (!fv) 11703 t.selectByIndex(-1); 11704 } 11705 }, 11706 11707 selectByIndex : function(idx) { 11708 var t = this, e, o, label; 11709 11710 t.marked = {}; 11711 11712 if (idx != t.selectedIndex) { 11713 e = DOM.get(t.id + '_text'); 11714 label = DOM.get(t.id + '_voiceDesc'); 11715 o = t.items[idx]; 11716 11717 if (o) { 11718 t.selectedValue = o.value; 11719 t.selectedIndex = idx; 11720 DOM.setHTML(e, DOM.encode(o.title)); 11721 DOM.setHTML(label, t.settings.title + " - " + o.title); 11722 DOM.removeClass(e, 'mceTitle'); 11723 DOM.setAttrib(t.id, 'aria-valuenow', o.title); 11724 } else { 11725 DOM.setHTML(e, DOM.encode(t.settings.title)); 11726 DOM.setHTML(label, DOM.encode(t.settings.title)); 11727 DOM.addClass(e, 'mceTitle'); 11728 t.selectedValue = t.selectedIndex = null; 11729 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); 11730 } 11731 e = 0; 11732 } 11733 }, 11734 11735 mark : function(value) { 11736 this.marked[value] = true; 11737 }, 11738 11739 add : function(n, v, o) { 11740 var t = this; 11741 11742 o = o || {}; 11743 o = tinymce.extend(o, { 11744 title : n, 11745 value : v 11746 }); 11747 11748 t.items.push(o); 11749 t.onAdd.dispatch(t, o); 11750 }, 11751 11752 getLength : function() { 11753 return this.items.length; 11754 }, 11755 11756 renderHTML : function() { 11757 var h = '', t = this, s = t.settings, cp = t.classPrefix; 11758 11759 h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>'; 11760 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 11761 h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>'; 11762 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>'; 11763 h += '</tr></tbody></table></span>'; 11764 11765 return h; 11766 }, 11767 11768 showMenu : function() { 11769 var t = this, p2, e = DOM.get(this.id), m; 11770 11771 if (t.isDisabled() || t.items.length === 0) 11772 return; 11773 11774 if (t.menu && t.menu.isMenuVisible) 11775 return t.hideMenu(); 11776 11777 if (!t.isMenuRendered) { 11778 t.renderMenu(); 11779 t.isMenuRendered = true; 11780 } 11781 11782 p2 = DOM.getPos(e); 11783 11784 m = t.menu; 11785 m.settings.offset_x = p2.x; 11786 m.settings.offset_y = p2.y; 11787 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus 11788 11789 // Select in menu 11790 each(t.items, function(o) { 11791 if (m.items[o.id]) { 11792 m.items[o.id].setSelected(0); 11793 } 11794 }); 11795 11796 each(t.items, function(o) { 11797 if (m.items[o.id] && t.marked[o.value]) { 11798 m.items[o.id].setSelected(1); 11799 } 11800 11801 if (o.value === t.selectedValue) { 11802 m.items[o.id].setSelected(1); 11803 } 11804 }); 11805 11806 m.showMenu(0, e.clientHeight); 11807 11808 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 11809 DOM.addClass(t.id, t.classPrefix + 'Selected'); 11810 11811 //DOM.get(t.id + '_text').focus(); 11812 }, 11813 11814 hideMenu : function(e) { 11815 var t = this; 11816 11817 if (t.menu && t.menu.isMenuVisible) { 11818 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11819 11820 // Prevent double toogles by canceling the mouse click event to the button 11821 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) 11822 return; 11823 11824 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 11825 DOM.removeClass(t.id, t.classPrefix + 'Selected'); 11826 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 11827 t.menu.hideMenu(); 11828 } 11829 } 11830 }, 11831 11832 renderMenu : function() { 11833 var t = this, m; 11834 11835 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 11836 menu_line : 1, 11837 'class' : t.classPrefix + 'Menu mceNoIcons', 11838 max_width : 250, 11839 max_height : 150 11840 }); 11841 11842 m.onHideMenu.add(function() { 11843 t.hideMenu(); 11844 t.focus(); 11845 }); 11846 11847 m.add({ 11848 title : t.settings.title, 11849 'class' : 'mceMenuItemTitle', 11850 onclick : function() { 11851 if (t.settings.onselect('') !== false) 11852 t.select(''); // Must be runned after 11853 } 11854 }); 11855 11856 each(t.items, function(o) { 11857 // No value then treat it as a title 11858 if (o.value === undef) { 11859 m.add({ 11860 title : o.title, 11861 role : "option", 11862 'class' : 'mceMenuItemTitle', 11863 onclick : function() { 11864 if (t.settings.onselect('') !== false) 11865 t.select(''); // Must be runned after 11866 } 11867 }); 11868 } else { 11869 o.id = DOM.uniqueId(); 11870 o.role= "option"; 11871 o.onclick = function() { 11872 if (t.settings.onselect(o.value) !== false) 11873 t.select(o.value); // Must be runned after 11874 }; 11875 11876 m.add(o); 11877 } 11878 }); 11879 11880 t.onRenderMenu.dispatch(t, m); 11881 t.menu = m; 11882 }, 11883 11884 postRender : function() { 11885 var t = this, cp = t.classPrefix; 11886 11887 Event.add(t.id, 'click', t.showMenu, t); 11888 Event.add(t.id, 'keydown', function(evt) { 11889 if (evt.keyCode == 32) { // Space 11890 t.showMenu(evt); 11891 Event.cancel(evt); 11892 } 11893 }); 11894 Event.add(t.id, 'focus', function() { 11895 if (!t._focused) { 11896 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { 11897 if (e.keyCode == 40) { 11898 t.showMenu(); 11899 Event.cancel(e); 11900 } 11901 }); 11902 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { 11903 var v; 11904 if (e.keyCode == 13) { 11905 // Fake select on enter 11906 v = t.selectedValue; 11907 t.selectedValue = null; // Needs to be null to fake change 11908 Event.cancel(e); 11909 t.settings.onselect(v); 11910 } 11911 }); 11912 } 11913 11914 t._focused = 1; 11915 }); 11916 Event.add(t.id, 'blur', function() { 11917 Event.remove(t.id, 'keydown', t.keyDownHandler); 11918 Event.remove(t.id, 'keypress', t.keyPressHandler); 11919 t._focused = 0; 11920 }); 11921 11922 // Old IE doesn't have hover on all elements 11923 if (tinymce.isIE6 || !DOM.boxModel) { 11924 Event.add(t.id, 'mouseover', function() { 11925 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11926 DOM.addClass(t.id, cp + 'Hover'); 11927 }); 11928 11929 Event.add(t.id, 'mouseout', function() { 11930 if (!DOM.hasClass(t.id, cp + 'Disabled')) 11931 DOM.removeClass(t.id, cp + 'Hover'); 11932 }); 11933 } 11934 11935 t.onPostRender.dispatch(t, DOM.get(t.id)); 11936 }, 11937 11938 destroy : function() { 11939 this.parent(); 11940 11941 Event.clear(this.id + '_text'); 11942 Event.clear(this.id + '_open'); 11943 } 11944 }); 11945 })(tinymce); 11946 11947 (function(tinymce) { 11948 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef; 11949 11950 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', { 11951 NativeListBox : function(id, s) { 11952 this.parent(id, s); 11953 this.classPrefix = 'mceNativeListBox'; 11954 }, 11955 11956 setDisabled : function(s) { 11957 DOM.get(this.id).disabled = s; 11958 this.setAriaProperty('disabled', s); 11959 }, 11960 11961 isDisabled : function() { 11962 return DOM.get(this.id).disabled; 11963 }, 11964 11965 select : function(va) { 11966 var t = this, fv, f; 11967 11968 if (va == undef) 11969 return t.selectByIndex(-1); 11970 11971 // Is string or number make function selector 11972 if (va && typeof(va)=="function") 11973 f = va; 11974 else { 11975 f = function(v) { 11976 return v == va; 11977 }; 11978 } 11979 11980 // Do we need to do something? 11981 if (va != t.selectedValue) { 11982 // Find item 11983 each(t.items, function(o, i) { 11984 if (f(o.value)) { 11985 fv = 1; 11986 t.selectByIndex(i); 11987 return false; 11988 } 11989 }); 11990 11991 if (!fv) 11992 t.selectByIndex(-1); 11993 } 11994 }, 11995 11996 selectByIndex : function(idx) { 11997 DOM.get(this.id).selectedIndex = idx + 1; 11998 this.selectedValue = this.items[idx] ? this.items[idx].value : null; 11999 }, 12000 12001 add : function(n, v, a) { 12002 var o, t = this; 12003 12004 a = a || {}; 12005 a.value = v; 12006 12007 if (t.isRendered()) 12008 DOM.add(DOM.get(this.id), 'option', a, n); 12009 12010 o = { 12011 title : n, 12012 value : v, 12013 attribs : a 12014 }; 12015 12016 t.items.push(o); 12017 t.onAdd.dispatch(t, o); 12018 }, 12019 12020 getLength : function() { 12021 return this.items.length; 12022 }, 12023 12024 renderHTML : function() { 12025 var h, t = this; 12026 12027 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --'); 12028 12029 each(t.items, function(it) { 12030 h += DOM.createHTML('option', {value : it.value}, it.title); 12031 }); 12032 12033 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); 12034 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); 12035 return h; 12036 }, 12037 12038 postRender : function() { 12039 var t = this, ch, changeListenerAdded = true; 12040 12041 t.rendered = true; 12042 12043 function onChange(e) { 12044 var v = t.items[e.target.selectedIndex - 1]; 12045 12046 if (v && (v = v.value)) { 12047 t.onChange.dispatch(t, v); 12048 12049 if (t.settings.onselect) 12050 t.settings.onselect(v); 12051 } 12052 }; 12053 12054 Event.add(t.id, 'change', onChange); 12055 12056 // Accessibility keyhandler 12057 Event.add(t.id, 'keydown', function(e) { 12058 var bf; 12059 12060 Event.remove(t.id, 'change', ch); 12061 changeListenerAdded = false; 12062 12063 bf = Event.add(t.id, 'blur', function() { 12064 if (changeListenerAdded) return; 12065 changeListenerAdded = true; 12066 Event.add(t.id, 'change', onChange); 12067 Event.remove(t.id, 'blur', bf); 12068 }); 12069 12070 //prevent default left and right keys on chrome - so that the keyboard navigation is used. 12071 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { 12072 return Event.prevent(e); 12073 } 12074 12075 if (e.keyCode == 13 || e.keyCode == 32) { 12076 onChange(e); 12077 return Event.cancel(e); 12078 } 12079 }); 12080 12081 t.onPostRender.dispatch(t, DOM.get(t.id)); 12082 } 12083 }); 12084 })(tinymce); 12085 12086 (function(tinymce) { 12087 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12088 12089 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { 12090 MenuButton : function(id, s, ed) { 12091 this.parent(id, s, ed); 12092 12093 this.onRenderMenu = new tinymce.util.Dispatcher(this); 12094 12095 s.menu_container = s.menu_container || DOM.doc.body; 12096 }, 12097 12098 showMenu : function() { 12099 var t = this, p1, p2, e = DOM.get(t.id), m; 12100 12101 if (t.isDisabled()) 12102 return; 12103 12104 if (!t.isMenuRendered) { 12105 t.renderMenu(); 12106 t.isMenuRendered = true; 12107 } 12108 12109 if (t.isMenuVisible) 12110 return t.hideMenu(); 12111 12112 p1 = DOM.getPos(t.settings.menu_container); 12113 p2 = DOM.getPos(e); 12114 12115 m = t.menu; 12116 m.settings.offset_x = p2.x; 12117 m.settings.offset_y = p2.y; 12118 m.settings.vp_offset_x = p2.x; 12119 m.settings.vp_offset_y = p2.y; 12120 m.settings.keyboard_focus = t._focused; 12121 m.showMenu(0, e.firstChild.clientHeight); 12122 12123 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12124 t.setState('Selected', 1); 12125 12126 t.isMenuVisible = 1; 12127 }, 12128 12129 renderMenu : function() { 12130 var t = this, m; 12131 12132 m = t.settings.control_manager.createDropMenu(t.id + '_menu', { 12133 menu_line : 1, 12134 'class' : this.classPrefix + 'Menu', 12135 icons : t.settings.icons 12136 }); 12137 12138 m.onHideMenu.add(function() { 12139 t.hideMenu(); 12140 t.focus(); 12141 }); 12142 12143 t.onRenderMenu.dispatch(t, m); 12144 t.menu = m; 12145 }, 12146 12147 hideMenu : function(e) { 12148 var t = this; 12149 12150 // Prevent double toogles by canceling the mouse click event to the button 12151 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) 12152 return; 12153 12154 if (!e || !DOM.getParent(e.target, '.mceMenu')) { 12155 t.setState('Selected', 0); 12156 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12157 if (t.menu) 12158 t.menu.hideMenu(); 12159 } 12160 12161 t.isMenuVisible = 0; 12162 }, 12163 12164 postRender : function() { 12165 var t = this, s = t.settings; 12166 12167 Event.add(t.id, 'click', function() { 12168 if (!t.isDisabled()) { 12169 if (s.onclick) 12170 s.onclick(t.value); 12171 12172 t.showMenu(); 12173 } 12174 }); 12175 } 12176 }); 12177 })(tinymce); 12178 12179 (function(tinymce) { 12180 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; 12181 12182 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { 12183 SplitButton : function(id, s, ed) { 12184 this.parent(id, s, ed); 12185 this.classPrefix = 'mceSplitButton'; 12186 }, 12187 12188 renderHTML : function() { 12189 var h, t = this, s = t.settings, h1; 12190 12191 h = '<tbody><tr>'; 12192 12193 if (s.image) 12194 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); 12195 else 12196 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); 12197 12198 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); 12199 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12200 12201 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>'); 12202 h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>'; 12203 12204 h += '</tr></tbody>'; 12205 h = DOM.createHTML('table', { role: 'presentation', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); 12206 return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); 12207 }, 12208 12209 postRender : function() { 12210 var t = this, s = t.settings, activate; 12211 12212 if (s.onclick) { 12213 activate = function(evt) { 12214 if (!t.isDisabled()) { 12215 s.onclick(t.value); 12216 Event.cancel(evt); 12217 } 12218 }; 12219 Event.add(t.id + '_action', 'click', activate); 12220 Event.add(t.id, ['click', 'keydown'], function(evt) { 12221 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; 12222 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { 12223 activate(); 12224 Event.cancel(evt); 12225 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { 12226 t.showMenu(); 12227 Event.cancel(evt); 12228 } 12229 }); 12230 } 12231 12232 Event.add(t.id + '_open', 'click', function (evt) { 12233 t.showMenu(); 12234 Event.cancel(evt); 12235 }); 12236 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); 12237 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); 12238 12239 // Old IE doesn't have hover on all elements 12240 if (tinymce.isIE6 || !DOM.boxModel) { 12241 Event.add(t.id, 'mouseover', function() { 12242 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12243 DOM.addClass(t.id, 'mceSplitButtonHover'); 12244 }); 12245 12246 Event.add(t.id, 'mouseout', function() { 12247 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled')) 12248 DOM.removeClass(t.id, 'mceSplitButtonHover'); 12249 }); 12250 } 12251 }, 12252 12253 destroy : function() { 12254 this.parent(); 12255 12256 Event.clear(this.id + '_action'); 12257 Event.clear(this.id + '_open'); 12258 Event.clear(this.id); 12259 } 12260 }); 12261 })(tinymce); 12262 12263 (function(tinymce) { 12264 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; 12265 12266 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { 12267 ColorSplitButton : function(id, s, ed) { 12268 var t = this; 12269 12270 t.parent(id, s, ed); 12271 12272 t.settings = s = tinymce.extend({ 12273 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', 12274 grid_width : 8, 12275 default_color : '#888888' 12276 }, t.settings); 12277 12278 t.onShowMenu = new tinymce.util.Dispatcher(t); 12279 12280 t.onHideMenu = new tinymce.util.Dispatcher(t); 12281 12282 t.value = s.default_color; 12283 }, 12284 12285 showMenu : function() { 12286 var t = this, r, p, e, p2; 12287 12288 if (t.isDisabled()) 12289 return; 12290 12291 if (!t.isMenuRendered) { 12292 t.renderMenu(); 12293 t.isMenuRendered = true; 12294 } 12295 12296 if (t.isMenuVisible) 12297 return t.hideMenu(); 12298 12299 e = DOM.get(t.id); 12300 DOM.show(t.id + '_menu'); 12301 DOM.addClass(e, 'mceSplitButtonSelected'); 12302 p2 = DOM.getPos(e); 12303 DOM.setStyles(t.id + '_menu', { 12304 left : p2.x, 12305 top : p2.y + e.firstChild.clientHeight, 12306 zIndex : 200000 12307 }); 12308 e = 0; 12309 12310 Event.add(DOM.doc, 'mousedown', t.hideMenu, t); 12311 t.onShowMenu.dispatch(t); 12312 12313 if (t._focused) { 12314 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { 12315 if (e.keyCode == 27) 12316 t.hideMenu(); 12317 }); 12318 12319 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link 12320 } 12321 12322 t.keyboardNav = new tinymce.ui.KeyboardNavigation({ 12323 root: t.id + '_menu', 12324 items: DOM.select('a', t.id + '_menu'), 12325 onCancel: function() { 12326 t.hideMenu(); 12327 t.focus(); 12328 } 12329 }); 12330 12331 t.keyboardNav.focus(); 12332 t.isMenuVisible = 1; 12333 }, 12334 12335 hideMenu : function(e) { 12336 var t = this; 12337 12338 if (t.isMenuVisible) { 12339 // Prevent double toogles by canceling the mouse click event to the button 12340 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) 12341 return; 12342 12343 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { 12344 DOM.removeClass(t.id, 'mceSplitButtonSelected'); 12345 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); 12346 Event.remove(t.id + '_menu', 'keydown', t._keyHandler); 12347 DOM.hide(t.id + '_menu'); 12348 } 12349 12350 t.isMenuVisible = 0; 12351 t.onHideMenu.dispatch(); 12352 t.keyboardNav.destroy(); 12353 } 12354 }, 12355 12356 renderMenu : function() { 12357 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; 12358 12359 w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); 12360 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); 12361 DOM.add(m, 'span', {'class' : 'mceMenuLine'}); 12362 12363 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); 12364 tb = DOM.add(n, 'tbody'); 12365 12366 // Generate color grid 12367 i = 0; 12368 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) { 12369 c = c.replace(/^#/, ''); 12370 12371 if (!i--) { 12372 tr = DOM.add(tb, 'tr'); 12373 i = s.grid_width - 1; 12374 } 12375 12376 n = DOM.add(tr, 'td'); 12377 var settings = { 12378 href : 'javascript:;', 12379 style : { 12380 backgroundColor : '#' + c 12381 }, 12382 'title': t.editor.getLang('colors.' + c, c), 12383 'data-mce-color' : '#' + c 12384 }; 12385 12386 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE. 12387 if (!tinymce.isIE ) { 12388 settings.role = 'option'; 12389 } 12390 12391 n = DOM.add(n, 'a', settings); 12392 12393 if (t.editor.forcedHighContrastMode) { 12394 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); 12395 if (n.getContext && (context = n.getContext("2d"))) { 12396 context.fillStyle = '#' + c; 12397 context.fillRect(0, 0, 16, 16); 12398 } else { 12399 // No point leaving a canvas element around if it's not supported for drawing on anyway. 12400 DOM.remove(n); 12401 } 12402 } 12403 }); 12404 12405 if (s.more_colors_func) { 12406 n = DOM.add(tb, 'tr'); 12407 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); 12408 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); 12409 12410 Event.add(n, 'click', function(e) { 12411 s.more_colors_func.call(s.more_colors_scope || this); 12412 return Event.cancel(e); // Cancel to fix onbeforeunload problem 12413 }); 12414 } 12415 12416 DOM.addClass(m, 'mceColorSplitMenu'); 12417 12418 // Prevent IE from scrolling and hindering click to occur #4019 12419 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); 12420 12421 Event.add(t.id + '_menu', 'click', function(e) { 12422 var c; 12423 12424 e = DOM.getParent(e.target, 'a', tb); 12425 12426 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) 12427 t.setColor(c); 12428 12429 return false; // Prevent IE auto save warning 12430 }); 12431 12432 return w; 12433 }, 12434 12435 setColor : function(c) { 12436 this.displayColor(c); 12437 this.hideMenu(); 12438 this.settings.onselect(c); 12439 }, 12440 12441 displayColor : function(c) { 12442 var t = this; 12443 12444 DOM.setStyle(t.id + '_preview', 'backgroundColor', c); 12445 12446 t.value = c; 12447 }, 12448 12449 postRender : function() { 12450 var t = this, id = t.id; 12451 12452 t.parent(); 12453 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); 12454 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value); 12455 }, 12456 12457 destroy : function() { 12458 var self = this; 12459 12460 self.parent(); 12461 12462 Event.clear(self.id + '_menu'); 12463 Event.clear(self.id + '_more'); 12464 DOM.remove(self.id + '_menu'); 12465 12466 if (self.keyboardNav) { 12467 self.keyboardNav.destroy(); 12468 } 12469 } 12470 }); 12471 })(tinymce); 12472 12473 (function(tinymce) { 12474 // Shorten class names 12475 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; 12476 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { 12477 renderHTML : function() { 12478 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; 12479 12480 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">'); 12481 //TODO: ACC test this out - adding a role = application for getting the landmarks working well. 12482 h.push("<span role='application'>"); 12483 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>'); 12484 each(controls, function(toolbar) { 12485 h.push(toolbar.renderHTML()); 12486 }); 12487 h.push("</span>"); 12488 h.push('</div>'); 12489 12490 return h.join(''); 12491 }, 12492 12493 focus : function() { 12494 var t = this; 12495 dom.get(t.id).focus(); 12496 }, 12497 12498 postRender : function() { 12499 var t = this, items = []; 12500 12501 each(t.controls, function(toolbar) { 12502 each (toolbar.controls, function(control) { 12503 if (control.id) { 12504 items.push(control); 12505 } 12506 }); 12507 }); 12508 12509 t.keyNav = new tinymce.ui.KeyboardNavigation({ 12510 root: t.id, 12511 items: items, 12512 onCancel: function() { 12513 //Move focus if webkit so that navigation back will read the item. 12514 if (tinymce.isWebKit) { 12515 dom.get(t.editor.id+"_ifr").focus(); 12516 } 12517 t.editor.focus(); 12518 }, 12519 excludeFromTabOrder: !t.settings.tab_focus_toolbar 12520 }); 12521 }, 12522 12523 destroy : function() { 12524 var self = this; 12525 12526 self.parent(); 12527 self.keyNav.destroy(); 12528 Event.clear(self.id); 12529 } 12530 }); 12531 })(tinymce); 12532 12533 (function(tinymce) { 12534 // Shorten class names 12535 var dom = tinymce.DOM, each = tinymce.each; 12536 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 12537 renderHTML : function() { 12538 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; 12539 12540 cl = t.controls; 12541 for (i=0; i<cl.length; i++) { 12542 // Get current control, prev control, next control and if the control is a list box or not 12543 co = cl[i]; 12544 pr = cl[i - 1]; 12545 nx = cl[i + 1]; 12546 12547 // Add toolbar start 12548 if (i === 0) { 12549 c = 'mceToolbarStart'; 12550 12551 if (co.Button) 12552 c += ' mceToolbarStartButton'; 12553 else if (co.SplitButton) 12554 c += ' mceToolbarStartSplitButton'; 12555 else if (co.ListBox) 12556 c += ' mceToolbarStartListBox'; 12557 12558 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12559 } 12560 12561 // Add toolbar end before list box and after the previous button 12562 // This is to fix the o2k7 editor skins 12563 if (pr && co.ListBox) { 12564 if (pr.Button || pr.SplitButton) 12565 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->')); 12566 } 12567 12568 // Render control HTML 12569 12570 // IE 8 quick fix, needed to propertly generate a hit area for anchors 12571 if (dom.stdMode) 12572 h += '<td style="position: relative">' + co.renderHTML() + '</td>'; 12573 else 12574 h += '<td>' + co.renderHTML() + '</td>'; 12575 12576 // Add toolbar start after list box and before the next button 12577 // This is to fix the o2k7 editor skins 12578 if (nx && co.ListBox) { 12579 if (nx.Button || nx.SplitButton) 12580 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->')); 12581 } 12582 } 12583 12584 c = 'mceToolbarEnd'; 12585 12586 if (co.Button) 12587 c += ' mceToolbarEndButton'; 12588 else if (co.SplitButton) 12589 c += ' mceToolbarEndSplitButton'; 12590 else if (co.ListBox) 12591 c += ' mceToolbarEndListBox'; 12592 12593 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->')); 12594 12595 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>'); 12596 } 12597 }); 12598 })(tinymce); 12599 12600 (function(tinymce) { 12601 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; 12602 12603 tinymce.create('tinymce.AddOnManager', { 12604 AddOnManager : function() { 12605 var self = this; 12606 12607 self.items = []; 12608 self.urls = {}; 12609 self.lookup = {}; 12610 self.onAdd = new Dispatcher(self); 12611 }, 12612 12613 get : function(n) { 12614 if (this.lookup[n]) { 12615 return this.lookup[n].instance; 12616 } else { 12617 return undefined; 12618 } 12619 }, 12620 12621 dependencies : function(n) { 12622 var result; 12623 if (this.lookup[n]) { 12624 result = this.lookup[n].dependencies; 12625 } 12626 return result || []; 12627 }, 12628 12629 requireLangPack : function(n) { 12630 var s = tinymce.settings; 12631 12632 if (s && s.language && s.language_load !== false) 12633 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); 12634 }, 12635 12636 add : function(id, o, dependencies) { 12637 this.items.push(o); 12638 this.lookup[id] = {instance:o, dependencies:dependencies}; 12639 this.onAdd.dispatch(this, id, o); 12640 12641 return o; 12642 }, 12643 createUrl: function(baseUrl, dep) { 12644 if (typeof dep === "object") { 12645 return dep 12646 } else { 12647 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 12648 } 12649 }, 12650 12651 addComponents: function(pluginName, scripts) { 12652 var pluginUrl = this.urls[pluginName]; 12653 tinymce.each(scripts, function(script){ 12654 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 12655 }); 12656 }, 12657 12658 load : function(n, u, cb, s) { 12659 var t = this, url = u; 12660 12661 function loadDependencies() { 12662 var dependencies = t.dependencies(n); 12663 tinymce.each(dependencies, function(dep) { 12664 var newUrl = t.createUrl(u, dep); 12665 t.load(newUrl.resource, newUrl, undefined, undefined); 12666 }); 12667 if (cb) { 12668 if (s) { 12669 cb.call(s); 12670 } else { 12671 cb.call(tinymce.ScriptLoader); 12672 } 12673 } 12674 } 12675 12676 if (t.urls[n]) 12677 return; 12678 if (typeof u === "object") 12679 url = u.prefix + u.resource + u.suffix; 12680 12681 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) 12682 url = tinymce.baseURL + '/' + url; 12683 12684 t.urls[n] = url.substring(0, url.lastIndexOf('/')); 12685 12686 if (t.lookup[n]) { 12687 loadDependencies(); 12688 } else { 12689 tinymce.ScriptLoader.add(url, loadDependencies, s); 12690 } 12691 } 12692 }); 12693 12694 // Create plugin and theme managers 12695 tinymce.PluginManager = new tinymce.AddOnManager(); 12696 tinymce.ThemeManager = new tinymce.AddOnManager(); 12697 }(tinymce)); 12698 12699 (function(tinymce) { 12700 // Shorten names 12701 var each = tinymce.each, extend = tinymce.extend, 12702 DOM = tinymce.DOM, Event = tinymce.dom.Event, 12703 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 12704 explode = tinymce.explode, 12705 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0; 12706 12707 // Setup some URLs where the editor API is located and where the document is 12708 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 12709 if (!/[\/\\]$/.test(tinymce.documentBaseURL)) 12710 tinymce.documentBaseURL += '/'; 12711 12712 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); 12713 12714 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL); 12715 12716 // Add before unload listener 12717 // This was required since IE was leaking memory if you added and removed beforeunload listeners 12718 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event 12719 tinymce.onBeforeUnload = new Dispatcher(tinymce); 12720 12721 // Must be on window or IE will leak if the editor is placed in frame or iframe 12722 Event.add(window, 'beforeunload', function(e) { 12723 tinymce.onBeforeUnload.dispatch(tinymce, e); 12724 }); 12725 12726 tinymce.onAddEditor = new Dispatcher(tinymce); 12727 12728 tinymce.onRemoveEditor = new Dispatcher(tinymce); 12729 12730 tinymce.EditorManager = extend(tinymce, { 12731 editors : [], 12732 12733 i18n : {}, 12734 12735 activeEditor : null, 12736 12737 init : function(s) { 12738 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed; 12739 12740 function createId(elm) { 12741 var id = elm.id; 12742 12743 // Use element id, or unique name or generate a unique id 12744 if (!id) { 12745 id = elm.name; 12746 12747 if (id && !DOM.get(id)) { 12748 id = elm.name; 12749 } else { 12750 // Generate unique name 12751 id = DOM.uniqueId(); 12752 } 12753 12754 elm.setAttribute('id', id); 12755 } 12756 12757 return id; 12758 }; 12759 12760 function execCallback(se, n, s) { 12761 var f = se[n]; 12762 12763 if (!f) 12764 return; 12765 12766 if (tinymce.is(f, 'string')) { 12767 s = f.replace(/\.\w+$/, ''); 12768 s = s ? tinymce.resolve(s) : 0; 12769 f = tinymce.resolve(f); 12770 } 12771 12772 return f.apply(s || this, Array.prototype.slice.call(arguments, 2)); 12773 }; 12774 12775 function hasClass(n, c) { 12776 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); 12777 }; 12778 12779 t.settings = s; 12780 12781 // Legacy call 12782 Event.bind(window, 'ready', function() { 12783 var l, co; 12784 12785 execCallback(s, 'onpageload'); 12786 12787 switch (s.mode) { 12788 case "exact": 12789 l = s.elements || ''; 12790 12791 if(l.length > 0) { 12792 each(explode(l), function(v) { 12793 if (DOM.get(v)) { 12794 ed = new tinymce.Editor(v, s); 12795 el.push(ed); 12796 ed.render(1); 12797 } else { 12798 each(document.forms, function(f) { 12799 each(f.elements, function(e) { 12800 if (e.name === v) { 12801 v = 'mce_editor_' + instanceCounter++; 12802 DOM.setAttrib(e, 'id', v); 12803 12804 ed = new tinymce.Editor(v, s); 12805 el.push(ed); 12806 ed.render(1); 12807 } 12808 }); 12809 }); 12810 } 12811 }); 12812 } 12813 break; 12814 12815 case "textareas": 12816 case "specific_textareas": 12817 each(DOM.select('textarea'), function(elm) { 12818 if (s.editor_deselector && hasClass(elm, s.editor_deselector)) 12819 return; 12820 12821 if (!s.editor_selector || hasClass(elm, s.editor_selector)) { 12822 ed = new tinymce.Editor(createId(elm), s); 12823 el.push(ed); 12824 ed.render(1); 12825 } 12826 }); 12827 break; 12828 12829 default: 12830 if (s.types) { 12831 // Process type specific selector 12832 each(s.types, function(type) { 12833 each(DOM.select(type.selector), function(elm) { 12834 var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type)); 12835 el.push(editor); 12836 editor.render(1); 12837 }); 12838 }); 12839 } else if (s.selector) { 12840 // Process global selector 12841 each(DOM.select(s.selector), function(elm) { 12842 var editor = new tinymce.Editor(createId(elm), s); 12843 el.push(editor); 12844 editor.render(1); 12845 }); 12846 } 12847 } 12848 12849 // Call onInit when all editors are initialized 12850 if (s.oninit) { 12851 l = co = 0; 12852 12853 each(el, function(ed) { 12854 co++; 12855 12856 if (!ed.initialized) { 12857 // Wait for it 12858 ed.onInit.add(function() { 12859 l++; 12860 12861 // All done 12862 if (l == co) 12863 execCallback(s, 'oninit'); 12864 }); 12865 } else 12866 l++; 12867 12868 // All done 12869 if (l == co) 12870 execCallback(s, 'oninit'); 12871 }); 12872 } 12873 }); 12874 }, 12875 12876 get : function(id) { 12877 if (id === undef) 12878 return this.editors; 12879 12880 return this.editors[id]; 12881 }, 12882 12883 getInstanceById : function(id) { 12884 return this.get(id); 12885 }, 12886 12887 add : function(editor) { 12888 var self = this, editors = self.editors; 12889 12890 // Add named and index editor instance 12891 editors[editor.id] = editor; 12892 editors.push(editor); 12893 12894 self._setActive(editor); 12895 self.onAddEditor.dispatch(self, editor); 12896 12897 12898 return editor; 12899 }, 12900 12901 remove : function(editor) { 12902 var t = this, i, editors = t.editors; 12903 12904 // Not in the collection 12905 if (!editors[editor.id]) 12906 return null; 12907 12908 delete editors[editor.id]; 12909 12910 for (i = 0; i < editors.length; i++) { 12911 if (editors[i] == editor) { 12912 editors.splice(i, 1); 12913 break; 12914 } 12915 } 12916 12917 // Select another editor since the active one was removed 12918 if (t.activeEditor == editor) 12919 t._setActive(editors[0]); 12920 12921 editor.destroy(); 12922 t.onRemoveEditor.dispatch(t, editor); 12923 12924 return editor; 12925 }, 12926 12927 execCommand : function(c, u, v) { 12928 var t = this, ed = t.get(v), w; 12929 12930 function clr() { 12931 ed.destroy(); 12932 w.detachEvent('onunload', clr); 12933 w = w.tinyMCE = w.tinymce = null; // IE leak 12934 }; 12935 12936 // Manager commands 12937 switch (c) { 12938 case "mceFocus": 12939 ed.focus(); 12940 return true; 12941 12942 case "mceAddEditor": 12943 case "mceAddControl": 12944 if (!t.get(v)) 12945 new tinymce.Editor(v, t.settings).render(); 12946 12947 return true; 12948 12949 case "mceAddFrameControl": 12950 w = v.window; 12951 12952 // Add tinyMCE global instance and tinymce namespace to specified window 12953 w.tinyMCE = tinyMCE; 12954 w.tinymce = tinymce; 12955 12956 tinymce.DOM.doc = w.document; 12957 tinymce.DOM.win = w; 12958 12959 ed = new tinymce.Editor(v.element_id, v); 12960 ed.render(); 12961 12962 // Fix IE memory leaks 12963 if (tinymce.isIE) { 12964 w.attachEvent('onunload', clr); 12965 } 12966 12967 v.page_window = null; 12968 12969 return true; 12970 12971 case "mceRemoveEditor": 12972 case "mceRemoveControl": 12973 if (ed) 12974 ed.remove(); 12975 12976 return true; 12977 12978 case 'mceToggleEditor': 12979 if (!ed) { 12980 t.execCommand('mceAddControl', 0, v); 12981 return true; 12982 } 12983 12984 if (ed.isHidden()) 12985 ed.show(); 12986 else 12987 ed.hide(); 12988 12989 return true; 12990 } 12991 12992 // Run command on active editor 12993 if (t.activeEditor) 12994 return t.activeEditor.execCommand(c, u, v); 12995 12996 return false; 12997 }, 12998 12999 execInstanceCommand : function(id, c, u, v) { 13000 var ed = this.get(id); 13001 13002 if (ed) 13003 return ed.execCommand(c, u, v); 13004 13005 return false; 13006 }, 13007 13008 triggerSave : function() { 13009 each(this.editors, function(e) { 13010 e.save(); 13011 }); 13012 }, 13013 13014 addI18n : function(p, o) { 13015 var lo, i18n = this.i18n; 13016 13017 if (!tinymce.is(p, 'string')) { 13018 each(p, function(o, lc) { 13019 each(o, function(o, g) { 13020 each(o, function(o, k) { 13021 if (g === 'common') 13022 i18n[lc + '.' + k] = o; 13023 else 13024 i18n[lc + '.' + g + '.' + k] = o; 13025 }); 13026 }); 13027 }); 13028 } else { 13029 each(o, function(o, k) { 13030 i18n[p + '.' + k] = o; 13031 }); 13032 } 13033 }, 13034 13035 // Private methods 13036 13037 _setActive : function(editor) { 13038 this.selectedInstance = this.activeEditor = editor; 13039 } 13040 }); 13041 })(tinymce); 13042 13043 (function(tinymce) { 13044 // Shorten these names 13045 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, 13046 each = tinymce.each, isGecko = tinymce.isGecko, 13047 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is, 13048 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, 13049 explode = tinymce.explode; 13050 13051 tinymce.create('tinymce.Editor', { 13052 Editor : function(id, settings) { 13053 var self = this, TRUE = true; 13054 13055 self.settings = settings = extend({ 13056 id : id, 13057 language : 'en', 13058 theme : 'advanced', 13059 skin : 'default', 13060 delta_width : 0, 13061 delta_height : 0, 13062 popup_css : '', 13063 plugins : '', 13064 document_base_url : tinymce.documentBaseURL, 13065 add_form_submit_trigger : TRUE, 13066 submit_patch : TRUE, 13067 add_unload_trigger : TRUE, 13068 convert_urls : TRUE, 13069 relative_urls : TRUE, 13070 remove_script_host : TRUE, 13071 table_inline_editing : false, 13072 object_resizing : TRUE, 13073 accessibility_focus : TRUE, 13074 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll 13075 visual : TRUE, 13076 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large', 13077 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 13078 apply_source_formatting : TRUE, 13079 directionality : 'ltr', 13080 forced_root_block : 'p', 13081 hidden_input : TRUE, 13082 padd_empty_editor : TRUE, 13083 render_ui : TRUE, 13084 indentation : '30px', 13085 fix_table_elements : TRUE, 13086 inline_styles : TRUE, 13087 convert_fonts_to_spans : TRUE, 13088 indent : 'simple', 13089 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13090 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist', 13091 validate : TRUE, 13092 entity_encoding : 'named', 13093 url_converter : self.convertURL, 13094 url_converter_scope : self, 13095 ie7_compat : TRUE 13096 }, settings); 13097 13098 self.id = self.editorId = id; 13099 13100 self.isNotDirty = false; 13101 13102 self.plugins = {}; 13103 13104 self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, { 13105 base_uri : tinyMCE.baseURI 13106 }); 13107 13108 self.baseURI = tinymce.baseURI; 13109 13110 self.contentCSS = []; 13111 13112 self.contentStyles = []; 13113 13114 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 13115 self.setupEvents(); 13116 13117 // Internal command handler objects 13118 self.execCommands = {}; 13119 self.queryStateCommands = {}; 13120 self.queryValueCommands = {}; 13121 13122 // Call setup 13123 self.execCallback('setup', self); 13124 }, 13125 13126 render : function(nst) { 13127 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader; 13128 13129 // Page is not loaded yet, wait for it 13130 if (!Event.domLoaded) { 13131 Event.add(window, 'ready', function() { 13132 t.render(); 13133 }); 13134 return; 13135 } 13136 13137 tinyMCE.settings = s; 13138 13139 // Element not found, then skip initialization 13140 if (!t.getElement()) 13141 return; 13142 13143 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 13144 // here since the browser says it has contentEditable support but there is no visible caret. 13145 if (tinymce.isIDevice && !tinymce.isIOS5) 13146 return; 13147 13148 // Add hidden input for non input elements inside form elements 13149 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form')) 13150 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id); 13151 13152 // Hide target element early to prevent content flashing 13153 if (!s.content_editable) { 13154 t.orgVisibility = t.getElement().style.visibility; 13155 t.getElement().style.visibility = 'hidden'; 13156 } 13157 13158 if (tinymce.WindowManager) 13159 t.windowManager = new tinymce.WindowManager(t); 13160 13161 if (s.encoding == 'xml') { 13162 t.onGetContent.add(function(ed, o) { 13163 if (o.save) 13164 o.content = DOM.encode(o.content); 13165 }); 13166 } 13167 13168 if (s.add_form_submit_trigger) { 13169 t.onSubmit.addToTop(function() { 13170 if (t.initialized) { 13171 t.save(); 13172 t.isNotDirty = 1; 13173 } 13174 }); 13175 } 13176 13177 if (s.add_unload_trigger) { 13178 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() { 13179 if (t.initialized && !t.destroyed && !t.isHidden()) 13180 t.save({format : 'raw', no_events : true}); 13181 }); 13182 } 13183 13184 tinymce.addUnload(t.destroy, t); 13185 13186 if (s.submit_patch) { 13187 t.onBeforeRenderUI.add(function() { 13188 var n = t.getElement().form; 13189 13190 if (!n) 13191 return; 13192 13193 // Already patched 13194 if (n._mceOldSubmit) 13195 return; 13196 13197 // Check page uses id="submit" or name="submit" for it's submit button 13198 if (!n.submit.nodeType && !n.submit.length) { 13199 t.formElement = n; 13200 n._mceOldSubmit = n.submit; 13201 n.submit = function() { 13202 // Save all instances 13203 tinymce.triggerSave(); 13204 t.isNotDirty = 1; 13205 13206 return t.formElement._mceOldSubmit(t.formElement); 13207 }; 13208 } 13209 13210 n = null; 13211 }); 13212 } 13213 13214 // Load scripts 13215 function loadScripts() { 13216 if (s.language && s.language_load !== false) 13217 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js'); 13218 13219 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme]) 13220 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js'); 13221 13222 each(explode(s.plugins), function(p) { 13223 if (p &&!PluginManager.urls[p]) { 13224 if (p.charAt(0) == '-') { 13225 p = p.substr(1, p.length); 13226 var dependencies = PluginManager.dependencies(p); 13227 each(dependencies, function(dep) { 13228 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'}; 13229 dep = PluginManager.createUrl(defaultSettings, dep); 13230 PluginManager.load(dep.resource, dep); 13231 }); 13232 } else { 13233 // Skip safari plugin, since it is removed as of 3.3b1 13234 if (p == 'safari') { 13235 return; 13236 } 13237 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'}); 13238 } 13239 } 13240 }); 13241 13242 // Init when que is loaded 13243 sl.loadQueue(function() { 13244 if (!t.removed) 13245 t.init(); 13246 }); 13247 }; 13248 13249 loadScripts(); 13250 }, 13251 13252 init : function() { 13253 var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = []; 13254 13255 tinymce.add(t); 13256 13257 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area')); 13258 13259 if (s.theme) { 13260 if (typeof s.theme != "function") { 13261 s.theme = s.theme.replace(/-/, ''); 13262 o = ThemeManager.get(s.theme); 13263 t.theme = new o(); 13264 13265 if (t.theme.init) 13266 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, '')); 13267 } else { 13268 t.theme = s.theme; 13269 } 13270 } 13271 13272 function initPlugin(p) { 13273 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po; 13274 if (c && tinymce.inArray(initializedPlugins,p) === -1) { 13275 each(PluginManager.dependencies(p), function(dep){ 13276 initPlugin(dep); 13277 }); 13278 po = new c(t, u); 13279 13280 t.plugins[p] = po; 13281 13282 if (po.init) { 13283 po.init(t, u); 13284 initializedPlugins.push(p); 13285 } 13286 } 13287 } 13288 13289 // Create all plugins 13290 each(explode(s.plugins.replace(/\-/g, '')), initPlugin); 13291 13292 // Setup popup CSS path(s) 13293 if (s.popup_css !== false) { 13294 if (s.popup_css) 13295 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css); 13296 else 13297 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css"); 13298 } 13299 13300 if (s.popup_css_add) 13301 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add); 13302 13303 t.controlManager = new tinymce.ControlManager(t); 13304 13305 // Enables users to override the control factory 13306 t.onBeforeRenderUI.dispatch(t, t.controlManager); 13307 13308 // Measure box 13309 if (s.render_ui && t.theme) { 13310 t.orgDisplay = e.style.display; 13311 13312 if (typeof s.theme != "function") { 13313 w = s.width || e.style.width || e.offsetWidth; 13314 h = s.height || e.style.height || e.offsetHeight; 13315 mh = s.min_height || 100; 13316 re = /^[0-9\.]+(|px)$/i; 13317 13318 if (re.test('' + w)) 13319 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100); 13320 13321 if (re.test('' + h)) 13322 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh); 13323 13324 // Render UI 13325 o = t.theme.renderUI({ 13326 targetNode : e, 13327 width : w, 13328 height : h, 13329 deltaWidth : s.delta_width, 13330 deltaHeight : s.delta_height 13331 }); 13332 13333 // Resize editor 13334 DOM.setStyles(o.sizeContainer || o.editorContainer, { 13335 width : w, 13336 height : h 13337 }); 13338 13339 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : ''); 13340 if (h < mh) 13341 h = mh; 13342 } else { 13343 o = s.theme(t, e); 13344 13345 // Convert element type to id:s 13346 if (o.editorContainer.nodeType) { 13347 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent"; 13348 } 13349 13350 // Convert element type to id:s 13351 if (o.iframeContainer.nodeType) { 13352 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer"; 13353 } 13354 13355 // Use specified iframe height or the targets offsetHeight 13356 h = o.iframeHeight || e.offsetHeight; 13357 13358 // Store away the selection when it's changed to it can be restored later with a editor.focus() call 13359 if (isIE) { 13360 t.onInit.add(function(ed) { 13361 ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() { 13362 ed.lastIERng = ed.selection.getRng(); 13363 }); 13364 }); 13365 } 13366 } 13367 13368 t.editorContainer = o.editorContainer; 13369 } 13370 13371 // Load specified content CSS last 13372 if (s.content_css) { 13373 each(explode(s.content_css), function(u) { 13374 t.contentCSS.push(t.documentBaseURI.toAbsolute(u)); 13375 }); 13376 } 13377 13378 // Content editable mode ends here 13379 if (s.content_editable) { 13380 e = n = o = null; // Fix IE leak 13381 return t.initContentBody(); 13382 } 13383 13384 // User specified a document.domain value 13385 if (document.domain && location.hostname != document.domain) 13386 tinymce.relaxedDomain = document.domain; 13387 13388 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">'; 13389 13390 // We only need to override paths if we have to 13391 // IE has a bug where it remove site absolute urls to relative ones if this is specified 13392 if (s.document_base_url != tinymce.documentBaseURL) 13393 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />'; 13394 13395 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 13396 if (s.ie7_compat) 13397 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 13398 else 13399 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'; 13400 13401 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 13402 13403 // Load the CSS by injecting them into the HTML this will reduce "flicker" 13404 for (i = 0; i < t.contentCSS.length; i++) { 13405 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />'; 13406 } 13407 13408 t.contentCSS = []; 13409 13410 bi = s.body_id || 'tinymce'; 13411 if (bi.indexOf('=') != -1) { 13412 bi = t.getParam('body_id', '', 'hash'); 13413 bi = bi[t.id] || bi; 13414 } 13415 13416 bc = s.body_class || ''; 13417 if (bc.indexOf('=') != -1) { 13418 bc = t.getParam('body_class', '', 'hash'); 13419 bc = bc[t.id] || ''; 13420 } 13421 13422 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>'; 13423 13424 // Domain relaxing enabled, then set document domain 13425 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { 13426 // We need to write the contents here in IE since multiple writes messes up refresh button and back button 13427 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()'; 13428 } 13429 13430 // Create iframe 13431 // TODO: ACC add the appropriate description on this. 13432 n = DOM.add(o.iframeContainer, 'iframe', { 13433 id : t.id + "_ifr", 13434 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 13435 frameBorder : '0', 13436 allowTransparency : "true", 13437 title : s.aria_label, 13438 style : { 13439 width : '100%', 13440 height : h, 13441 display : 'block' // Important for Gecko to render the iframe correctly 13442 } 13443 }); 13444 13445 t.contentAreaContainer = o.iframeContainer; 13446 13447 if (o.editorContainer) { 13448 DOM.get(o.editorContainer).style.display = t.orgDisplay; 13449 } 13450 13451 // Restore visibility on target element 13452 e.style.visibility = t.orgVisibility; 13453 13454 DOM.get(t.id).style.display = 'none'; 13455 DOM.setAttrib(t.id, 'aria-hidden', true); 13456 13457 if (!tinymce.relaxedDomain || !u) 13458 t.initContentBody(); 13459 13460 e = n = o = null; // Cleanup 13461 }, 13462 13463 initContentBody : function() { 13464 var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText; 13465 13466 // Setup iframe body 13467 if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) { 13468 doc.open(); 13469 doc.write(self.iframeHTML); 13470 doc.close(); 13471 13472 if (tinymce.relaxedDomain) 13473 doc.domain = tinymce.relaxedDomain; 13474 } 13475 13476 if (settings.content_editable) { 13477 DOM.addClass(targetElm, 'mceContentBody'); 13478 self.contentDocument = doc = settings.content_document || document; 13479 self.contentWindow = settings.content_window || window; 13480 self.bodyElement = targetElm; 13481 13482 // Prevent leak in IE 13483 settings.content_document = settings.content_window = null; 13484 } 13485 13486 // It will not steal focus while setting contentEditable 13487 body = self.getBody(); 13488 body.disabled = true; 13489 13490 if (!settings.readonly) 13491 body.contentEditable = self.getParam('content_editable_state', true); 13492 13493 body.disabled = false; 13494 13495 self.schema = new tinymce.html.Schema(settings); 13496 13497 self.dom = new tinymce.dom.DOMUtils(doc, { 13498 keep_values : true, 13499 url_converter : self.convertURL, 13500 url_converter_scope : self, 13501 hex_colors : settings.force_hex_style_colors, 13502 class_filter : settings.class_filter, 13503 update_styles : true, 13504 root_element : settings.content_editable ? self.id : null, 13505 schema : self.schema 13506 }); 13507 13508 self.parser = new tinymce.html.DomParser(settings, self.schema); 13509 13510 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 13511 self.parser.addAttributeFilter('src,href,style', function(nodes, name) { 13512 var i = nodes.length, node, dom = self.dom, value, internalName; 13513 13514 while (i--) { 13515 node = nodes[i]; 13516 value = node.attr(name); 13517 internalName = 'data-mce-' + name; 13518 13519 // Add internal attribute if we need to we don't on a refresh of the document 13520 if (!node.attributes.map[internalName]) { 13521 if (name === "style") 13522 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name)); 13523 else 13524 node.attr(internalName, self.convertURL(value, name, node.name)); 13525 } 13526 } 13527 }); 13528 13529 // Keep scripts from executing 13530 self.parser.addNodeFilter('script', function(nodes, name) { 13531 var i = nodes.length, node; 13532 13533 while (i--) { 13534 node = nodes[i]; 13535 node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript')); 13536 } 13537 }); 13538 13539 self.parser.addNodeFilter('#cdata', function(nodes, name) { 13540 var i = nodes.length, node; 13541 13542 while (i--) { 13543 node = nodes[i]; 13544 node.type = 8; 13545 node.name = '#comment'; 13546 node.value = '[CDATA[' + node.value + ']]'; 13547 } 13548 }); 13549 13550 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { 13551 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 13552 13553 while (i--) { 13554 node = nodes[i]; 13555 13556 if (node.isEmpty(nonEmptyElements)) 13557 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; 13558 } 13559 }); 13560 13561 self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema); 13562 13563 self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self); 13564 13565 self.formatter = new tinymce.Formatter(self); 13566 13567 self.undoManager = new tinymce.UndoManager(self); 13568 13569 self.forceBlocks = new tinymce.ForceBlocks(self); 13570 self.enterKey = new tinymce.EnterKey(self); 13571 self.editorCommands = new tinymce.EditorCommands(self); 13572 13573 self.onExecCommand.add(function(editor, command) { 13574 // Don't refresh the select lists until caret move 13575 if (!/^(FontName|FontSize)$/.test(command)) 13576 self.nodeChanged(); 13577 }); 13578 13579 // Pass through 13580 self.serializer.onPreProcess.add(function(se, o) { 13581 return self.onPreProcess.dispatch(self, o, se); 13582 }); 13583 13584 self.serializer.onPostProcess.add(function(se, o) { 13585 return self.onPostProcess.dispatch(self, o, se); 13586 }); 13587 13588 self.onPreInit.dispatch(self); 13589 13590 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) 13591 doc.body.spellcheck = false; 13592 13593 if (!settings.readonly) { 13594 self.bindNativeEvents(); 13595 } 13596 13597 self.controlManager.onPostRender.dispatch(self, self.controlManager); 13598 self.onPostRender.dispatch(self); 13599 13600 self.quirks = tinymce.util.Quirks(self); 13601 13602 if (settings.directionality) 13603 body.dir = settings.directionality; 13604 13605 if (settings.nowrap) 13606 body.style.whiteSpace = "nowrap"; 13607 13608 if (settings.protect) { 13609 self.onBeforeSetContent.add(function(ed, o) { 13610 each(settings.protect, function(pattern) { 13611 o.content = o.content.replace(pattern, function(str) { 13612 return '<!--mce:protected ' + escape(str) + '-->'; 13613 }); 13614 }); 13615 }); 13616 } 13617 13618 // Add visual aids when new contents is added 13619 self.onSetContent.add(function() { 13620 self.addVisual(self.getBody()); 13621 }); 13622 13623 // Remove empty contents 13624 if (settings.padd_empty_editor) { 13625 self.onPostProcess.add(function(ed, o) { 13626 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 13627 }); 13628 } 13629 13630 self.load({initial : true, format : 'html'}); 13631 self.startContent = self.getContent({format : 'raw'}); 13632 13633 self.initialized = true; 13634 13635 self.onInit.dispatch(self); 13636 self.execCallback('setupcontent_callback', self.id, body, doc); 13637 self.execCallback('init_instance_callback', self); 13638 self.focus(true); 13639 self.nodeChanged({initial : true}); 13640 13641 // Add editor specific CSS styles 13642 if (self.contentStyles.length > 0) { 13643 contentCssText = ''; 13644 13645 each(self.contentStyles, function(style) { 13646 contentCssText += style + "\r\n"; 13647 }); 13648 13649 self.dom.addStyle(contentCssText); 13650 } 13651 13652 // Load specified content CSS last 13653 each(self.contentCSS, function(url) { 13654 self.dom.loadCSS(url); 13655 }); 13656 13657 // Handle auto focus 13658 if (settings.auto_focus) { 13659 setTimeout(function () { 13660 var ed = tinymce.get(settings.auto_focus); 13661 13662 ed.selection.select(ed.getBody(), 1); 13663 ed.selection.collapse(1); 13664 ed.getBody().focus(); 13665 ed.getWin().focus(); 13666 }, 100); 13667 } 13668 13669 // Clean up references for IE 13670 targetElm = doc = body = null; 13671 }, 13672 13673 focus : function(skip_focus) { 13674 var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body; 13675 13676 if (!skip_focus) { 13677 if (self.lastIERng) { 13678 selection.setRng(self.lastIERng); 13679 } 13680 13681 // Get selected control element 13682 ieRng = selection.getRng(); 13683 if (ieRng.item) { 13684 controlElm = ieRng.item(0); 13685 } 13686 13687 self._refreshContentEditable(); 13688 13689 // Focus the window iframe 13690 if (!contentEditable) { 13691 self.getWin().focus(); 13692 } 13693 13694 // Focus the body as well since it's contentEditable 13695 if (tinymce.isGecko || contentEditable) { 13696 body = self.getBody(); 13697 13698 // Check for setActive since it doesn't scroll to the element 13699 if (body.setActive) { 13700 body.setActive(); 13701 } else { 13702 body.focus(); 13703 } 13704 13705 if (contentEditable) { 13706 selection.normalize(); 13707 } 13708 } 13709 13710 // Restore selected control element 13711 // This is needed when for example an image is selected within a 13712 // layer a call to focus will then remove the control selection 13713 if (controlElm && controlElm.ownerDocument == doc) { 13714 ieRng = doc.body.createControlRange(); 13715 ieRng.addElement(controlElm); 13716 ieRng.select(); 13717 } 13718 } 13719 13720 if (tinymce.activeEditor != self) { 13721 if ((oed = tinymce.activeEditor) != null) 13722 oed.onDeactivate.dispatch(oed, self); 13723 13724 self.onActivate.dispatch(self, oed); 13725 } 13726 13727 tinymce._setActive(self); 13728 }, 13729 13730 execCallback : function(n) { 13731 var t = this, f = t.settings[n], s; 13732 13733 if (!f) 13734 return; 13735 13736 // Look through lookup 13737 if (t.callbackLookup && (s = t.callbackLookup[n])) { 13738 f = s.func; 13739 s = s.scope; 13740 } 13741 13742 if (is(f, 'string')) { 13743 s = f.replace(/\.\w+$/, ''); 13744 s = s ? tinymce.resolve(s) : 0; 13745 f = tinymce.resolve(f); 13746 t.callbackLookup = t.callbackLookup || {}; 13747 t.callbackLookup[n] = {func : f, scope : s}; 13748 } 13749 13750 return f.apply(s || t, Array.prototype.slice.call(arguments, 1)); 13751 }, 13752 13753 translate : function(s) { 13754 var c = this.settings.language || 'en', i18n = tinymce.i18n; 13755 13756 if (!s) 13757 return ''; 13758 13759 return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) { 13760 return i18n[c + '.' + b] || '{#' + b + '}'; 13761 }); 13762 }, 13763 13764 getLang : function(n, dv) { 13765 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}'); 13766 }, 13767 13768 getParam : function(n, dv, ty) { 13769 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o; 13770 13771 if (ty === 'hash') { 13772 o = {}; 13773 13774 if (is(v, 'string')) { 13775 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { 13776 v = v.split('='); 13777 13778 if (v.length > 1) 13779 o[tr(v[0])] = tr(v[1]); 13780 else 13781 o[tr(v[0])] = tr(v); 13782 }); 13783 } else 13784 o = v; 13785 13786 return o; 13787 } 13788 13789 return v; 13790 }, 13791 13792 nodeChanged : function(o) { 13793 var self = this, selection = self.selection, node; 13794 13795 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 13796 if (self.initialized) { 13797 o = o || {}; 13798 13799 // Get start node 13800 node = selection.getStart() || self.getBody(); 13801 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state 13802 13803 // Get parents and add them to object 13804 o.parents = []; 13805 self.dom.getParent(node, function(node) { 13806 if (node.nodeName == 'BODY') 13807 return true; 13808 13809 o.parents.push(node); 13810 }); 13811 13812 self.onNodeChange.dispatch( 13813 self, 13814 o ? o.controlManager || self.controlManager : self.controlManager, 13815 node, 13816 selection.isCollapsed(), 13817 o 13818 ); 13819 } 13820 }, 13821 13822 addButton : function(name, settings) { 13823 var self = this; 13824 13825 self.buttons = self.buttons || {}; 13826 self.buttons[name] = settings; 13827 }, 13828 13829 addCommand : function(name, callback, scope) { 13830 this.execCommands[name] = {func : callback, scope : scope || this}; 13831 }, 13832 13833 addQueryStateHandler : function(name, callback, scope) { 13834 this.queryStateCommands[name] = {func : callback, scope : scope || this}; 13835 }, 13836 13837 addQueryValueHandler : function(name, callback, scope) { 13838 this.queryValueCommands[name] = {func : callback, scope : scope || this}; 13839 }, 13840 13841 addShortcut : function(pa, desc, cmd_func, sc) { 13842 var t = this, c; 13843 13844 if (t.settings.custom_shortcuts === false) 13845 return false; 13846 13847 t.shortcuts = t.shortcuts || {}; 13848 13849 if (is(cmd_func, 'string')) { 13850 c = cmd_func; 13851 13852 cmd_func = function() { 13853 t.execCommand(c, false, null); 13854 }; 13855 } 13856 13857 if (is(cmd_func, 'object')) { 13858 c = cmd_func; 13859 13860 cmd_func = function() { 13861 t.execCommand(c[0], c[1], c[2]); 13862 }; 13863 } 13864 13865 each(explode(pa), function(pa) { 13866 var o = { 13867 func : cmd_func, 13868 scope : sc || this, 13869 desc : t.translate(desc), 13870 alt : false, 13871 ctrl : false, 13872 shift : false 13873 }; 13874 13875 each(explode(pa, '+'), function(v) { 13876 switch (v) { 13877 case 'alt': 13878 case 'ctrl': 13879 case 'shift': 13880 o[v] = true; 13881 break; 13882 13883 default: 13884 o.charCode = v.charCodeAt(0); 13885 o.keyCode = v.toUpperCase().charCodeAt(0); 13886 } 13887 }); 13888 13889 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o; 13890 }); 13891 13892 return true; 13893 }, 13894 13895 execCommand : function(cmd, ui, val, a) { 13896 var t = this, s = 0, o, st; 13897 13898 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) 13899 t.focus(); 13900 13901 a = extend({}, a); 13902 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a); 13903 if (a.terminate) 13904 return false; 13905 13906 // Command callback 13907 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) { 13908 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13909 return true; 13910 } 13911 13912 // Registred commands 13913 if (o = t.execCommands[cmd]) { 13914 st = o.func.call(o.scope, ui, val); 13915 13916 // Fall through on true 13917 if (st !== true) { 13918 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13919 return st; 13920 } 13921 } 13922 13923 // Plugin commands 13924 each(t.plugins, function(p) { 13925 if (p.execCommand && p.execCommand(cmd, ui, val)) { 13926 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13927 s = 1; 13928 return false; 13929 } 13930 }); 13931 13932 if (s) 13933 return true; 13934 13935 // Theme commands 13936 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) { 13937 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13938 return true; 13939 } 13940 13941 // Editor commands 13942 if (t.editorCommands.execCommand(cmd, ui, val)) { 13943 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13944 return true; 13945 } 13946 13947 // Browser commands 13948 t.getDoc().execCommand(cmd, ui, val); 13949 t.onExecCommand.dispatch(t, cmd, ui, val, a); 13950 }, 13951 13952 queryCommandState : function(cmd) { 13953 var t = this, o, s; 13954 13955 // Is hidden then return undefined 13956 if (t._isHidden()) 13957 return; 13958 13959 // Registred commands 13960 if (o = t.queryStateCommands[cmd]) { 13961 s = o.func.call(o.scope); 13962 13963 // Fall though on true 13964 if (s !== true) 13965 return s; 13966 } 13967 13968 // Registred commands 13969 o = t.editorCommands.queryCommandState(cmd); 13970 if (o !== -1) 13971 return o; 13972 13973 // Browser commands 13974 try { 13975 return this.getDoc().queryCommandState(cmd); 13976 } catch (ex) { 13977 // Fails sometimes see bug: 1896577 13978 } 13979 }, 13980 13981 queryCommandValue : function(c) { 13982 var t = this, o, s; 13983 13984 // Is hidden then return undefined 13985 if (t._isHidden()) 13986 return; 13987 13988 // Registred commands 13989 if (o = t.queryValueCommands[c]) { 13990 s = o.func.call(o.scope); 13991 13992 // Fall though on true 13993 if (s !== true) 13994 return s; 13995 } 13996 13997 // Registred commands 13998 o = t.editorCommands.queryCommandValue(c); 13999 if (is(o)) 14000 return o; 14001 14002 // Browser commands 14003 try { 14004 return this.getDoc().queryCommandValue(c); 14005 } catch (ex) { 14006 // Fails sometimes see bug: 1896577 14007 } 14008 }, 14009 14010 show : function() { 14011 var self = this; 14012 14013 DOM.show(self.getContainer()); 14014 DOM.hide(self.id); 14015 self.load(); 14016 }, 14017 14018 hide : function() { 14019 var self = this, doc = self.getDoc(); 14020 14021 // Fixed bug where IE has a blinking cursor left from the editor 14022 if (isIE && doc) 14023 doc.execCommand('SelectAll'); 14024 14025 // We must save before we hide so Safari doesn't crash 14026 self.save(); 14027 DOM.hide(self.getContainer()); 14028 DOM.setStyle(self.id, 'display', self.orgDisplay); 14029 }, 14030 14031 isHidden : function() { 14032 return !DOM.isHidden(this.id); 14033 }, 14034 14035 setProgressState : function(b, ti, o) { 14036 this.onSetProgressState.dispatch(this, b, ti, o); 14037 14038 return b; 14039 }, 14040 14041 load : function(o) { 14042 var t = this, e = t.getElement(), h; 14043 14044 if (e) { 14045 o = o || {}; 14046 o.load = true; 14047 14048 // Double encode existing entities in the value 14049 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o); 14050 o.element = e; 14051 14052 if (!o.no_events) 14053 t.onLoadContent.dispatch(t, o); 14054 14055 o.element = e = null; 14056 14057 return h; 14058 } 14059 }, 14060 14061 save : function(o) { 14062 var t = this, e = t.getElement(), h, f; 14063 14064 if (!e || !t.initialized) 14065 return; 14066 14067 o = o || {}; 14068 o.save = true; 14069 14070 o.element = e; 14071 h = o.content = t.getContent(o); 14072 14073 if (!o.no_events) 14074 t.onSaveContent.dispatch(t, o); 14075 14076 h = o.content; 14077 14078 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) { 14079 e.innerHTML = h; 14080 14081 // Update hidden form element 14082 if (f = DOM.getParent(t.id, 'form')) { 14083 each(f.elements, function(e) { 14084 if (e.name == t.id) { 14085 e.value = h; 14086 return false; 14087 } 14088 }); 14089 } 14090 } else 14091 e.value = h; 14092 14093 o.element = e = null; 14094 14095 return h; 14096 }, 14097 14098 setContent : function(content, args) { 14099 var self = this, rootNode, body = self.getBody(), forcedRootBlockName; 14100 14101 // Setup args object 14102 args = args || {}; 14103 args.format = args.format || 'html'; 14104 args.set = true; 14105 args.content = content; 14106 14107 // Do preprocessing 14108 if (!args.no_events) 14109 self.onBeforeSetContent.dispatch(self, args); 14110 14111 content = args.content; 14112 14113 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 14114 // It will also be impossible to place the caret in the editor unless there is a BR element present 14115 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { 14116 forcedRootBlockName = self.settings.forced_root_block; 14117 if (forcedRootBlockName) 14118 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>'; 14119 else 14120 content = '<br data-mce-bogus="1">'; 14121 14122 body.innerHTML = content; 14123 self.selection.select(body, true); 14124 self.selection.collapse(true); 14125 return; 14126 } 14127 14128 // Parse and serialize the html 14129 if (args.format !== 'raw') { 14130 content = new tinymce.html.Serializer({}, self.schema).serialize( 14131 self.parser.parse(content) 14132 ); 14133 } 14134 14135 // Set the new cleaned contents to the editor 14136 args.content = tinymce.trim(content); 14137 self.dom.setHTML(body, args.content); 14138 14139 // Do post processing 14140 if (!args.no_events) 14141 self.onSetContent.dispatch(self, args); 14142 14143 // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise 14144 if (!self.settings.content_editable || document.activeElement === self.getBody()) { 14145 self.selection.normalize(); 14146 } 14147 14148 return args.content; 14149 }, 14150 14151 getContent : function(args) { 14152 var self = this, content; 14153 14154 // Setup args object 14155 args = args || {}; 14156 args.format = args.format || 'html'; 14157 args.get = true; 14158 args.getInner = true; 14159 14160 // Do preprocessing 14161 if (!args.no_events) 14162 self.onBeforeGetContent.dispatch(self, args); 14163 14164 // Get raw contents or by default the cleaned contents 14165 if (args.format == 'raw') 14166 content = self.getBody().innerHTML; 14167 else 14168 content = self.serializer.serialize(self.getBody(), args); 14169 14170 args.content = tinymce.trim(content); 14171 14172 // Do post processing 14173 if (!args.no_events) 14174 self.onGetContent.dispatch(self, args); 14175 14176 return args.content; 14177 }, 14178 14179 isDirty : function() { 14180 var self = this; 14181 14182 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; 14183 }, 14184 14185 getContainer : function() { 14186 var self = this; 14187 14188 if (!self.container) 14189 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 14190 14191 return self.container; 14192 }, 14193 14194 getContentAreaContainer : function() { 14195 return this.contentAreaContainer; 14196 }, 14197 14198 getElement : function() { 14199 return DOM.get(this.settings.content_element || this.id); 14200 }, 14201 14202 getWin : function() { 14203 var self = this, elm; 14204 14205 if (!self.contentWindow) { 14206 elm = DOM.get(self.id + "_ifr"); 14207 14208 if (elm) 14209 self.contentWindow = elm.contentWindow; 14210 } 14211 14212 return self.contentWindow; 14213 }, 14214 14215 getDoc : function() { 14216 var self = this, win; 14217 14218 if (!self.contentDocument) { 14219 win = self.getWin(); 14220 14221 if (win) 14222 self.contentDocument = win.document; 14223 } 14224 14225 return self.contentDocument; 14226 }, 14227 14228 getBody : function() { 14229 return this.bodyElement || this.getDoc().body; 14230 }, 14231 14232 convertURL : function(url, name, elm) { 14233 var self = this, settings = self.settings; 14234 14235 // Use callback instead 14236 if (settings.urlconverter_callback) 14237 return self.execCallback('urlconverter_callback', url, elm, true, name); 14238 14239 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 14240 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0) 14241 return url; 14242 14243 // Convert to relative 14244 if (settings.relative_urls) 14245 return self.documentBaseURI.toRelative(url); 14246 14247 // Convert to absolute 14248 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 14249 14250 return url; 14251 }, 14252 14253 addVisual : function(elm) { 14254 var self = this, settings = self.settings, dom = self.dom, cls; 14255 14256 elm = elm || self.getBody(); 14257 14258 if (!is(self.hasVisual)) 14259 self.hasVisual = settings.visual; 14260 14261 each(dom.select('table,a', elm), function(elm) { 14262 var value; 14263 14264 switch (elm.nodeName) { 14265 case 'TABLE': 14266 cls = settings.visual_table_class || 'mceItemTable'; 14267 value = dom.getAttrib(elm, 'border'); 14268 14269 if (!value || value == '0') { 14270 if (self.hasVisual) 14271 dom.addClass(elm, cls); 14272 else 14273 dom.removeClass(elm, cls); 14274 } 14275 14276 return; 14277 14278 case 'A': 14279 if (!dom.getAttrib(elm, 'href', false)) { 14280 value = dom.getAttrib(elm, 'name') || elm.id; 14281 cls = 'mceItemAnchor'; 14282 14283 if (value) { 14284 if (self.hasVisual) 14285 dom.addClass(elm, cls); 14286 else 14287 dom.removeClass(elm, cls); 14288 } 14289 } 14290 14291 return; 14292 } 14293 }); 14294 14295 self.onVisualAid.dispatch(self, elm, self.hasVisual); 14296 }, 14297 14298 remove : function() { 14299 var self = this, elm = self.getContainer(); 14300 14301 if (!self.removed) { 14302 self.removed = 1; // Cancels post remove event execution 14303 self.hide(); 14304 14305 // Don't clear the window or document if content editable 14306 // is enabled since other instances might still be present 14307 if (!self.settings.content_editable) { 14308 Event.unbind(self.getWin()); 14309 Event.unbind(self.getDoc()); 14310 } 14311 14312 Event.unbind(self.getBody()); 14313 Event.clear(elm); 14314 14315 self.execCallback('remove_instance_callback', self); 14316 self.onRemove.dispatch(self); 14317 14318 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command 14319 self.onExecCommand.listeners = []; 14320 14321 tinymce.remove(self); 14322 DOM.remove(elm); 14323 } 14324 }, 14325 14326 destroy : function(s) { 14327 var t = this; 14328 14329 // One time is enough 14330 if (t.destroyed) 14331 return; 14332 14333 // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message 14334 if (isGecko) { 14335 Event.unbind(t.getDoc()); 14336 Event.unbind(t.getWin()); 14337 Event.unbind(t.getBody()); 14338 } 14339 14340 if (!s) { 14341 tinymce.removeUnload(t.destroy); 14342 tinyMCE.onBeforeUnload.remove(t._beforeUnload); 14343 14344 // Manual destroy 14345 if (t.theme && t.theme.destroy) 14346 t.theme.destroy(); 14347 14348 // Destroy controls, selection and dom 14349 t.controlManager.destroy(); 14350 t.selection.destroy(); 14351 t.dom.destroy(); 14352 } 14353 14354 if (t.formElement) { 14355 t.formElement.submit = t.formElement._mceOldSubmit; 14356 t.formElement._mceOldSubmit = null; 14357 } 14358 14359 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; 14360 14361 if (t.selection) 14362 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; 14363 14364 t.destroyed = 1; 14365 }, 14366 14367 // Internal functions 14368 14369 _refreshContentEditable : function() { 14370 var self = this, body, parent; 14371 14372 // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again 14373 if (self._isHidden()) { 14374 body = self.getBody(); 14375 parent = body.parentNode; 14376 14377 parent.removeChild(body); 14378 parent.appendChild(body); 14379 14380 body.focus(); 14381 } 14382 }, 14383 14384 _isHidden : function() { 14385 var s; 14386 14387 if (!isGecko) 14388 return 0; 14389 14390 // Weird, wheres that cursor selection? 14391 s = this.selection.getSel(); 14392 return (!s || !s.rangeCount || s.rangeCount === 0); 14393 } 14394 }); 14395 })(tinymce); 14396 (function(tinymce) { 14397 var each = tinymce.each; 14398 14399 tinymce.Editor.prototype.setupEvents = function() { 14400 var self = this, settings = self.settings; 14401 14402 // Add events to the editor 14403 each([ 14404 'onPreInit', 14405 14406 'onBeforeRenderUI', 14407 14408 'onPostRender', 14409 14410 'onLoad', 14411 14412 'onInit', 14413 14414 'onRemove', 14415 14416 'onActivate', 14417 14418 'onDeactivate', 14419 14420 'onClick', 14421 14422 'onEvent', 14423 14424 'onMouseUp', 14425 14426 'onMouseDown', 14427 14428 'onDblClick', 14429 14430 'onKeyDown', 14431 14432 'onKeyUp', 14433 14434 'onKeyPress', 14435 14436 'onContextMenu', 14437 14438 'onSubmit', 14439 14440 'onReset', 14441 14442 'onPaste', 14443 14444 'onPreProcess', 14445 14446 'onPostProcess', 14447 14448 'onBeforeSetContent', 14449 14450 'onBeforeGetContent', 14451 14452 'onSetContent', 14453 14454 'onGetContent', 14455 14456 'onLoadContent', 14457 14458 'onSaveContent', 14459 14460 'onNodeChange', 14461 14462 'onChange', 14463 14464 'onBeforeExecCommand', 14465 14466 'onExecCommand', 14467 14468 'onUndo', 14469 14470 'onRedo', 14471 14472 'onVisualAid', 14473 14474 'onSetProgressState', 14475 14476 'onSetAttrib' 14477 ], function(name) { 14478 self[name] = new tinymce.util.Dispatcher(self); 14479 }); 14480 14481 // Handle legacy cleanup_callback option 14482 if (settings.cleanup_callback) { 14483 self.onBeforeSetContent.add(function(ed, o) { 14484 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14485 }); 14486 14487 self.onPreProcess.add(function(ed, o) { 14488 if (o.set) 14489 ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o); 14490 14491 if (o.get) 14492 ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o); 14493 }); 14494 14495 self.onPostProcess.add(function(ed, o) { 14496 if (o.set) 14497 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o); 14498 14499 if (o.get) 14500 o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o); 14501 }); 14502 } 14503 14504 // Handle legacy save_callback option 14505 if (settings.save_callback) { 14506 self.onGetContent.add(function(ed, o) { 14507 if (o.save) 14508 o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14509 }); 14510 } 14511 14512 // Handle legacy handle_event_callback option 14513 if (settings.handle_event_callback) { 14514 self.onEvent.add(function(ed, e, o) { 14515 if (self.execCallback('handle_event_callback', e, ed, o) === false) { 14516 e.preventDefault(); 14517 e.stopPropagation(); 14518 } 14519 }); 14520 } 14521 14522 // Handle legacy handle_node_change_callback option 14523 if (settings.handle_node_change_callback) { 14524 self.onNodeChange.add(function(ed, cm, n) { 14525 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed()); 14526 }); 14527 } 14528 14529 // Handle legacy save_callback option 14530 if (settings.save_callback) { 14531 self.onSaveContent.add(function(ed, o) { 14532 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody()); 14533 14534 if (h) 14535 o.content = h; 14536 }); 14537 } 14538 14539 // Handle legacy onchange_callback option 14540 if (settings.onchange_callback) { 14541 self.onChange.add(function(ed, l) { 14542 ed.execCallback('onchange_callback', ed, l); 14543 }); 14544 } 14545 }; 14546 14547 tinymce.Editor.prototype.bindNativeEvents = function() { 14548 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset 14549 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap; 14550 14551 nativeToDispatcherMap = { 14552 mouseup : 'onMouseUp', 14553 mousedown : 'onMouseDown', 14554 click : 'onClick', 14555 keyup : 'onKeyUp', 14556 keydown : 'onKeyDown', 14557 keypress : 'onKeyPress', 14558 submit : 'onSubmit', 14559 reset : 'onReset', 14560 contextmenu : 'onContextMenu', 14561 dblclick : 'onDblClick', 14562 paste : 'onPaste' // Doesn't work in all browsers yet 14563 }; 14564 14565 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown 14566 function eventHandler(evt, args) { 14567 var type = evt.type; 14568 14569 // Don't fire events when it's removed 14570 if (self.removed) 14571 return; 14572 14573 // Sends the native event out to a global dispatcher then to the specific event dispatcher 14574 if (self.onEvent.dispatch(self, evt, args) !== false) { 14575 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args); 14576 } 14577 }; 14578 14579 // Opera doesn't support focus event for contentEditable elements so we need to fake it 14580 function doOperaFocus(e) { 14581 self.focus(true); 14582 }; 14583 14584 function nodeChanged(ed, e) { 14585 // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything 14586 if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) { 14587 self.selection.normalize(); 14588 } 14589 14590 self.nodeChanged(); 14591 } 14592 14593 // Add DOM events 14594 each(nativeToDispatcherMap, function(dispatcherName, nativeName) { 14595 var root = settings.content_editable ? self.getBody() : self.getDoc(); 14596 14597 switch (nativeName) { 14598 case 'contextmenu': 14599 dom.bind(root, nativeName, eventHandler); 14600 break; 14601 14602 case 'paste': 14603 dom.bind(self.getBody(), nativeName, eventHandler); 14604 break; 14605 14606 case 'submit': 14607 case 'reset': 14608 dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler); 14609 break; 14610 14611 default: 14612 dom.bind(root, nativeName, eventHandler); 14613 } 14614 }); 14615 14616 // Set the editor as active when focused 14617 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) { 14618 self.focus(true); 14619 }); 14620 14621 if (settings.content_editable && tinymce.isOpera) { 14622 dom.bind(self.getBody(), 'click', doOperaFocus); 14623 dom.bind(self.getBody(), 'keydown', doOperaFocus); 14624 } 14625 14626 // Add node change handler 14627 self.onMouseUp.add(nodeChanged); 14628 14629 self.onKeyUp.add(function(ed, e) { 14630 var keyCode = e.keyCode; 14631 14632 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey) 14633 nodeChanged(ed, e); 14634 }); 14635 14636 // Add reset handler 14637 self.onReset.add(function() { 14638 self.setContent(self.startContent, {format : 'raw'}); 14639 }); 14640 14641 // Add shortcuts 14642 function handleShortcut(e, execute) { 14643 if (e.altKey || e.ctrlKey || e.metaKey) { 14644 each(self.shortcuts, function(shortcut) { 14645 var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey; 14646 14647 if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) 14648 return; 14649 14650 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 14651 e.preventDefault(); 14652 14653 if (execute) { 14654 shortcut.func.call(shortcut.scope); 14655 } 14656 14657 return true; 14658 } 14659 }); 14660 } 14661 }; 14662 14663 self.onKeyUp.add(function(ed, e) { 14664 handleShortcut(e); 14665 }); 14666 14667 self.onKeyPress.add(function(ed, e) { 14668 handleShortcut(e); 14669 }); 14670 14671 self.onKeyDown.add(function(ed, e) { 14672 handleShortcut(e, true); 14673 }); 14674 14675 if (tinymce.isOpera) { 14676 self.onClick.add(function(ed, e) { 14677 e.preventDefault(); 14678 }); 14679 } 14680 }; 14681 })(tinymce); 14682 (function(tinymce) { 14683 // Added for compression purposes 14684 var each = tinymce.each, undef, TRUE = true, FALSE = false; 14685 14686 tinymce.EditorCommands = function(editor) { 14687 var dom = editor.dom, 14688 selection = editor.selection, 14689 commands = {state: {}, exec : {}, value : {}}, 14690 settings = editor.settings, 14691 formatter = editor.formatter, 14692 bookmark; 14693 14694 function execCommand(command, ui, value) { 14695 var func; 14696 14697 command = command.toLowerCase(); 14698 if (func = commands.exec[command]) { 14699 func(command, ui, value); 14700 return TRUE; 14701 } 14702 14703 return FALSE; 14704 }; 14705 14706 function queryCommandState(command) { 14707 var func; 14708 14709 command = command.toLowerCase(); 14710 if (func = commands.state[command]) 14711 return func(command); 14712 14713 return -1; 14714 }; 14715 14716 function queryCommandValue(command) { 14717 var func; 14718 14719 command = command.toLowerCase(); 14720 if (func = commands.value[command]) 14721 return func(command); 14722 14723 return FALSE; 14724 }; 14725 14726 function addCommands(command_list, type) { 14727 type = type || 'exec'; 14728 14729 each(command_list, function(callback, command) { 14730 each(command.toLowerCase().split(','), function(command) { 14731 commands[type][command] = callback; 14732 }); 14733 }); 14734 }; 14735 14736 // Expose public methods 14737 tinymce.extend(this, { 14738 execCommand : execCommand, 14739 queryCommandState : queryCommandState, 14740 queryCommandValue : queryCommandValue, 14741 addCommands : addCommands 14742 }); 14743 14744 // Private methods 14745 14746 function execNativeCommand(command, ui, value) { 14747 if (ui === undef) 14748 ui = FALSE; 14749 14750 if (value === undef) 14751 value = null; 14752 14753 return editor.getDoc().execCommand(command, ui, value); 14754 }; 14755 14756 function isFormatMatch(name) { 14757 return formatter.match(name); 14758 }; 14759 14760 function toggleFormat(name, value) { 14761 formatter.toggle(name, value ? {value : value} : undef); 14762 }; 14763 14764 function storeSelection(type) { 14765 bookmark = selection.getBookmark(type); 14766 }; 14767 14768 function restoreSelection() { 14769 selection.moveToBookmark(bookmark); 14770 }; 14771 14772 // Add execCommand overrides 14773 addCommands({ 14774 // Ignore these, added for compatibility 14775 'mceResetDesignMode,mceBeginUndoLevel' : function() {}, 14776 14777 // Add undo manager logic 14778 'mceEndUndoLevel,mceAddUndoLevel' : function() { 14779 editor.undoManager.add(); 14780 }, 14781 14782 'Cut,Copy,Paste' : function(command) { 14783 var doc = editor.getDoc(), failed; 14784 14785 // Try executing the native command 14786 try { 14787 execNativeCommand(command); 14788 } catch (ex) { 14789 // Command failed 14790 failed = TRUE; 14791 } 14792 14793 // Present alert message about clipboard access not being available 14794 if (failed || !doc.queryCommandSupported(command)) { 14795 if (tinymce.isGecko) { 14796 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) { 14797 if (state) 14798 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank'); 14799 }); 14800 } else 14801 editor.windowManager.alert(editor.getLang('clipboard_no_support')); 14802 } 14803 }, 14804 14805 // Override unlink command 14806 unlink : function(command) { 14807 if (selection.isCollapsed()) 14808 selection.select(selection.getNode()); 14809 14810 execNativeCommand(command); 14811 selection.collapse(FALSE); 14812 }, 14813 14814 // Override justify commands to use the text formatter engine 14815 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 14816 var align = command.substring(7); 14817 14818 // Remove all other alignments first 14819 each('left,center,right,full'.split(','), function(name) { 14820 if (align != name) 14821 formatter.remove('align' + name); 14822 }); 14823 14824 toggleFormat('align' + align); 14825 execCommand('mceRepaint'); 14826 }, 14827 14828 // Override list commands to fix WebKit bug 14829 'InsertUnorderedList,InsertOrderedList' : function(command) { 14830 var listElm, listParent; 14831 14832 execNativeCommand(command); 14833 14834 // WebKit produces lists within block elements so we need to split them 14835 // we will replace the native list creation logic to custom logic later on 14836 // TODO: Remove this when the list creation logic is removed 14837 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 14838 if (listElm) { 14839 listParent = listElm.parentNode; 14840 14841 // If list is within a text block then split that block 14842 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 14843 storeSelection(); 14844 dom.split(listParent, listElm); 14845 restoreSelection(); 14846 } 14847 } 14848 }, 14849 14850 // Override commands to use the text formatter engine 14851 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 14852 toggleFormat(command); 14853 }, 14854 14855 // Override commands to use the text formatter engine 14856 'ForeColor,HiliteColor,FontName' : function(command, ui, value) { 14857 toggleFormat(command, value); 14858 }, 14859 14860 FontSize : function(command, ui, value) { 14861 var fontClasses, fontSizes; 14862 14863 // Convert font size 1-7 to styles 14864 if (value >= 1 && value <= 7) { 14865 fontSizes = tinymce.explode(settings.font_size_style_values); 14866 fontClasses = tinymce.explode(settings.font_size_classes); 14867 14868 if (fontClasses) 14869 value = fontClasses[value - 1] || value; 14870 else 14871 value = fontSizes[value - 1] || value; 14872 } 14873 14874 toggleFormat(command, value); 14875 }, 14876 14877 RemoveFormat : function(command) { 14878 formatter.remove(command); 14879 }, 14880 14881 mceBlockQuote : function(command) { 14882 toggleFormat('blockquote'); 14883 }, 14884 14885 FormatBlock : function(command, ui, value) { 14886 return toggleFormat(value || 'p'); 14887 }, 14888 14889 mceCleanup : function() { 14890 var bookmark = selection.getBookmark(); 14891 14892 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); 14893 14894 selection.moveToBookmark(bookmark); 14895 }, 14896 14897 mceRemoveNode : function(command, ui, value) { 14898 var node = value || selection.getNode(); 14899 14900 // Make sure that the body node isn't removed 14901 if (node != editor.getBody()) { 14902 storeSelection(); 14903 editor.dom.remove(node, TRUE); 14904 restoreSelection(); 14905 } 14906 }, 14907 14908 mceSelectNodeDepth : function(command, ui, value) { 14909 var counter = 0; 14910 14911 dom.getParent(selection.getNode(), function(node) { 14912 if (node.nodeType == 1 && counter++ == value) { 14913 selection.select(node); 14914 return FALSE; 14915 } 14916 }, editor.getBody()); 14917 }, 14918 14919 mceSelectNode : function(command, ui, value) { 14920 selection.select(value); 14921 }, 14922 14923 mceInsertContent : function(command, ui, value) { 14924 var parser, serializer, parentNode, rootNode, fragment, args, 14925 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement; 14926 14927 //selection.normalize(); 14928 14929 // Setup parser and serializer 14930 parser = editor.parser; 14931 serializer = new tinymce.html.Serializer({}, editor.schema); 14932 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; 14933 14934 // Run beforeSetContent handlers on the HTML to be inserted 14935 args = {content: value, format: 'html'}; 14936 selection.onBeforeSetContent.dispatch(selection, args); 14937 value = args.content; 14938 14939 // Add caret at end of contents if it's missing 14940 if (value.indexOf('{$caret}') == -1) 14941 value += '{$caret}'; 14942 14943 // Replace the caret marker with a span bookmark element 14944 value = value.replace(/\{\$caret\}/, bookmarkHtml); 14945 14946 // Insert node maker where we will insert the new HTML and get it's parent 14947 if (!selection.isCollapsed()) 14948 editor.getDoc().execCommand('Delete', false, null); 14949 14950 parentNode = selection.getNode(); 14951 14952 // Parse the fragment within the context of the parent node 14953 args = {context : parentNode.nodeName.toLowerCase()}; 14954 fragment = parser.parse(value, args); 14955 14956 // Move the caret to a more suitable location 14957 node = fragment.lastChild; 14958 if (node.attr('id') == 'mce_marker') { 14959 marker = node; 14960 14961 for (node = node.prev; node; node = node.walk(true)) { 14962 if (node.type == 3 || !dom.isBlock(node.name)) { 14963 node.parent.insert(marker, node, node.name === 'br'); 14964 break; 14965 } 14966 } 14967 } 14968 14969 // If parser says valid we can insert the contents into that parent 14970 if (!args.invalid) { 14971 value = serializer.serialize(fragment); 14972 14973 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 14974 node = parentNode.firstChild; 14975 node2 = parentNode.lastChild; 14976 if (!node || (node === node2 && node.nodeName === 'BR')) 14977 dom.setHTML(parentNode, value); 14978 else 14979 selection.setContent(value); 14980 } else { 14981 // If the fragment was invalid within that context then we need 14982 // to parse and process the parent it's inserted into 14983 14984 // Insert bookmark node and get the parent 14985 selection.setContent(bookmarkHtml); 14986 parentNode = editor.selection.getNode(); 14987 rootNode = editor.getBody(); 14988 14989 // Opera will return the document node when selection is in root 14990 if (parentNode.nodeType == 9) 14991 parentNode = node = rootNode; 14992 else 14993 node = parentNode; 14994 14995 // Find the ancestor just before the root element 14996 while (node !== rootNode) { 14997 parentNode = node; 14998 node = node.parentNode; 14999 } 15000 15001 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 15002 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 15003 value = serializer.serialize( 15004 parser.parse( 15005 // Need to replace by using a function since $ in the contents would otherwise be a problem 15006 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 15007 return serializer.serialize(fragment); 15008 }) 15009 ) 15010 ); 15011 15012 // Set the inner/outer HTML depending on if we are in the root or not 15013 if (parentNode == rootNode) 15014 dom.setHTML(rootNode, value); 15015 else 15016 dom.setOuterHTML(parentNode, value); 15017 } 15018 15019 marker = dom.get('mce_marker'); 15020 15021 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well 15022 nodeRect = dom.getRect(marker); 15023 viewPortRect = dom.getViewPort(editor.getWin()); 15024 15025 // Check if node is out side the viewport if it is then scroll to it 15026 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || 15027 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { 15028 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody(); 15029 viewportBodyElement.scrollLeft = nodeRect.x; 15030 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25; 15031 } 15032 15033 // Move selection before marker and remove it 15034 rng = dom.createRng(); 15035 15036 // If previous sibling is a text node set the selection to the end of that node 15037 node = marker.previousSibling; 15038 if (node && node.nodeType == 3) { 15039 rng.setStart(node, node.nodeValue.length); 15040 } else { 15041 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 15042 rng.setStartBefore(marker); 15043 rng.setEndBefore(marker); 15044 } 15045 15046 // Remove the marker node and set the new range 15047 dom.remove(marker); 15048 selection.setRng(rng); 15049 15050 // Dispatch after event and add any visual elements needed 15051 selection.onSetContent.dispatch(selection, args); 15052 editor.addVisual(); 15053 }, 15054 15055 mceInsertRawHTML : function(command, ui, value) { 15056 selection.setContent('tiny_mce_marker'); 15057 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); 15058 }, 15059 15060 mceToggleFormat : function(command, ui, value) { 15061 toggleFormat(value); 15062 }, 15063 15064 mceSetContent : function(command, ui, value) { 15065 editor.setContent(value); 15066 }, 15067 15068 'Indent,Outdent' : function(command) { 15069 var intentValue, indentUnit, value; 15070 15071 // Setup indent level 15072 intentValue = settings.indentation; 15073 indentUnit = /[a-z%]+$/i.exec(intentValue); 15074 intentValue = parseInt(intentValue); 15075 15076 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 15077 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 15078 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 15079 formatter.apply('div'); 15080 } 15081 15082 each(selection.getSelectedBlocks(), function(element) { 15083 if (command == 'outdent') { 15084 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue); 15085 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : ''); 15086 } else 15087 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit); 15088 }); 15089 } else 15090 execNativeCommand(command); 15091 }, 15092 15093 mceRepaint : function() { 15094 var bookmark; 15095 15096 if (tinymce.isGecko) { 15097 try { 15098 storeSelection(TRUE); 15099 15100 if (selection.getSel()) 15101 selection.getSel().selectAllChildren(editor.getBody()); 15102 15103 selection.collapse(TRUE); 15104 restoreSelection(); 15105 } catch (ex) { 15106 // Ignore 15107 } 15108 } 15109 }, 15110 15111 mceToggleFormat : function(command, ui, value) { 15112 formatter.toggle(value); 15113 }, 15114 15115 InsertHorizontalRule : function() { 15116 editor.execCommand('mceInsertContent', false, '<hr />'); 15117 }, 15118 15119 mceToggleVisualAid : function() { 15120 editor.hasVisual = !editor.hasVisual; 15121 editor.addVisual(); 15122 }, 15123 15124 mceReplaceContent : function(command, ui, value) { 15125 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); 15126 }, 15127 15128 mceInsertLink : function(command, ui, value) { 15129 var anchor; 15130 15131 if (typeof(value) == 'string') 15132 value = {href : value}; 15133 15134 anchor = dom.getParent(selection.getNode(), 'a'); 15135 15136 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 15137 value.href = value.href.replace(' ', '%20'); 15138 15139 // Remove existing links if there could be child links or that the href isn't specified 15140 if (!anchor || !value.href) { 15141 formatter.remove('link'); 15142 } 15143 15144 // Apply new link to selection 15145 if (value.href) { 15146 formatter.apply('link', value, anchor); 15147 } 15148 }, 15149 15150 selectAll : function() { 15151 var root = dom.getRoot(), rng = dom.createRng(); 15152 15153 rng.setStart(root, 0); 15154 rng.setEnd(root, root.childNodes.length); 15155 15156 editor.selection.setRng(rng); 15157 } 15158 }); 15159 15160 // Add queryCommandState overrides 15161 addCommands({ 15162 // Override justify commands 15163 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) { 15164 var name = 'align' + command.substring(7); 15165 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 15166 var matches = tinymce.map(nodes, function(node) { 15167 return !!formatter.matchNode(node, name); 15168 }); 15169 return tinymce.inArray(matches, TRUE) !== -1; 15170 }, 15171 15172 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { 15173 return isFormatMatch(command); 15174 }, 15175 15176 mceBlockQuote : function() { 15177 return isFormatMatch('blockquote'); 15178 }, 15179 15180 Outdent : function() { 15181 var node; 15182 15183 if (settings.inline_styles) { 15184 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15185 return TRUE; 15186 15187 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0) 15188 return TRUE; 15189 } 15190 15191 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')); 15192 }, 15193 15194 'InsertUnorderedList,InsertOrderedList' : function(command) { 15195 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL'); 15196 } 15197 }, 'state'); 15198 15199 // Add queryCommandValue overrides 15200 addCommands({ 15201 'FontSize,FontName' : function(command) { 15202 var value = 0, parent; 15203 15204 if (parent = dom.getParent(selection.getNode(), 'span')) { 15205 if (command == 'fontsize') 15206 value = parent.style.fontSize; 15207 else 15208 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 15209 } 15210 15211 return value; 15212 } 15213 }, 'value'); 15214 15215 // Add undo manager logic 15216 addCommands({ 15217 Undo : function() { 15218 editor.undoManager.undo(); 15219 }, 15220 15221 Redo : function() { 15222 editor.undoManager.redo(); 15223 } 15224 }); 15225 }; 15226 })(tinymce); 15227 15228 (function(tinymce) { 15229 var Dispatcher = tinymce.util.Dispatcher; 15230 15231 tinymce.UndoManager = function(editor) { 15232 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo; 15233 15234 function getContent() { 15235 // Remove whitespace before/after and remove pure bogus nodes 15236 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, '')); 15237 }; 15238 15239 function addNonTypingUndoLevel() { 15240 self.typing = false; 15241 self.add(); 15242 }; 15243 15244 // Create event instances 15245 onBeforeAdd = new Dispatcher(self); 15246 onAdd = new Dispatcher(self); 15247 onUndo = new Dispatcher(self); 15248 onRedo = new Dispatcher(self); 15249 15250 // Pass though onAdd event from UndoManager to Editor as onChange 15251 onAdd.add(function(undoman, level) { 15252 if (undoman.hasUndo()) 15253 return editor.onChange.dispatch(editor, level, undoman); 15254 }); 15255 15256 // Pass though onUndo event from UndoManager to Editor 15257 onUndo.add(function(undoman, level) { 15258 return editor.onUndo.dispatch(editor, level, undoman); 15259 }); 15260 15261 // Pass though onRedo event from UndoManager to Editor 15262 onRedo.add(function(undoman, level) { 15263 return editor.onRedo.dispatch(editor, level, undoman); 15264 }); 15265 15266 // Add initial undo level when the editor is initialized 15267 editor.onInit.add(function() { 15268 self.add(); 15269 }); 15270 15271 // Get position before an execCommand is processed 15272 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) { 15273 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15274 self.beforeChange(); 15275 } 15276 }); 15277 15278 // Add undo level after an execCommand call was made 15279 editor.onExecCommand.add(function(ed, cmd, ui, val, args) { 15280 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) { 15281 self.add(); 15282 } 15283 }); 15284 15285 // Add undo level on save contents, drag end and blur/focusout 15286 editor.onSaveContent.add(addNonTypingUndoLevel); 15287 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel); 15288 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) { 15289 if (!editor.removed && self.typing) { 15290 addNonTypingUndoLevel(); 15291 } 15292 }); 15293 15294 editor.onKeyUp.add(function(editor, e) { 15295 var keyCode = e.keyCode; 15296 15297 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 15298 addNonTypingUndoLevel(); 15299 } 15300 }); 15301 15302 editor.onKeyDown.add(function(editor, e) { 15303 var keyCode = e.keyCode; 15304 15305 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter 15306 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 15307 if (self.typing) { 15308 addNonTypingUndoLevel(); 15309 } 15310 15311 return; 15312 } 15313 15314 // If key isn't shift,ctrl,alt,capslock,metakey 15315 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) { 15316 self.beforeChange(); 15317 self.typing = true; 15318 self.add(); 15319 } 15320 }); 15321 15322 editor.onMouseDown.add(function(editor, e) { 15323 if (self.typing) { 15324 addNonTypingUndoLevel(); 15325 } 15326 }); 15327 15328 // Add keyboard shortcuts for undo/redo keys 15329 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo'); 15330 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo'); 15331 15332 self = { 15333 // Explose for debugging reasons 15334 data : data, 15335 15336 typing : false, 15337 15338 onBeforeAdd: onBeforeAdd, 15339 15340 onAdd : onAdd, 15341 15342 onUndo : onUndo, 15343 15344 onRedo : onRedo, 15345 15346 beforeChange : function() { 15347 beforeBookmark = editor.selection.getBookmark(2, true); 15348 }, 15349 15350 add : function(level) { 15351 var i, settings = editor.settings, lastLevel; 15352 15353 level = level || {}; 15354 level.content = getContent(); 15355 15356 self.onBeforeAdd.dispatch(self, level); 15357 15358 // Add undo level if needed 15359 lastLevel = data[index]; 15360 if (lastLevel && lastLevel.content == level.content) 15361 return null; 15362 15363 // Set before bookmark on previous level 15364 if (data[index]) 15365 data[index].beforeBookmark = beforeBookmark; 15366 15367 // Time to compress 15368 if (settings.custom_undo_redo_levels) { 15369 if (data.length > settings.custom_undo_redo_levels) { 15370 for (i = 0; i < data.length - 1; i++) 15371 data[i] = data[i + 1]; 15372 15373 data.length--; 15374 index = data.length; 15375 } 15376 } 15377 15378 // Get a non intrusive normalized bookmark 15379 level.bookmark = editor.selection.getBookmark(2, true); 15380 15381 // Crop array if needed 15382 if (index < data.length - 1) 15383 data.length = index + 1; 15384 15385 data.push(level); 15386 index = data.length - 1; 15387 15388 self.onAdd.dispatch(self, level); 15389 editor.isNotDirty = 0; 15390 15391 return level; 15392 }, 15393 15394 undo : function() { 15395 var level, i; 15396 15397 if (self.typing) { 15398 self.add(); 15399 self.typing = false; 15400 } 15401 15402 if (index > 0) { 15403 level = data[--index]; 15404 15405 editor.setContent(level.content, {format : 'raw'}); 15406 editor.selection.moveToBookmark(level.beforeBookmark); 15407 15408 self.onUndo.dispatch(self, level); 15409 } 15410 15411 return level; 15412 }, 15413 15414 redo : function() { 15415 var level; 15416 15417 if (index < data.length - 1) { 15418 level = data[++index]; 15419 15420 editor.setContent(level.content, {format : 'raw'}); 15421 editor.selection.moveToBookmark(level.bookmark); 15422 15423 self.onRedo.dispatch(self, level); 15424 } 15425 15426 return level; 15427 }, 15428 15429 clear : function() { 15430 data = []; 15431 index = 0; 15432 self.typing = false; 15433 }, 15434 15435 hasUndo : function() { 15436 return index > 0 || this.typing; 15437 }, 15438 15439 hasRedo : function() { 15440 return index < data.length - 1 && !this.typing; 15441 } 15442 }; 15443 15444 return self; 15445 }; 15446 })(tinymce); 15447 15448 tinymce.ForceBlocks = function(editor) { 15449 var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements(); 15450 15451 function addRootBlocks() { 15452 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument; 15453 15454 if (!node || node.nodeType !== 1 || !settings.forced_root_block) 15455 return; 15456 15457 // Check if node is wrapped in block 15458 while (node && node != rootNode) { 15459 if (blockElements[node.nodeName]) 15460 return; 15461 15462 node = node.parentNode; 15463 } 15464 15465 // Get current selection 15466 rng = selection.getRng(); 15467 if (rng.setStart) { 15468 startContainer = rng.startContainer; 15469 startOffset = rng.startOffset; 15470 endContainer = rng.endContainer; 15471 endOffset = rng.endOffset; 15472 } else { 15473 // Force control range into text range 15474 if (rng.item) { 15475 node = rng.item(0); 15476 rng = editor.getDoc().body.createTextRange(); 15477 rng.moveToElementText(node); 15478 } 15479 15480 isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc(); 15481 tmpRng = rng.duplicate(); 15482 tmpRng.collapse(true); 15483 startOffset = tmpRng.move('character', offset) * -1; 15484 15485 if (!tmpRng.collapsed) { 15486 tmpRng = rng.duplicate(); 15487 tmpRng.collapse(false); 15488 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 15489 } 15490 } 15491 15492 // Wrap non block elements and text nodes 15493 node = rootNode.firstChild; 15494 while (node) { 15495 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) { 15496 if (!rootBlockNode) { 15497 rootBlockNode = dom.create(settings.forced_root_block); 15498 node.parentNode.insertBefore(rootBlockNode, node); 15499 wrapped = true; 15500 } 15501 15502 tempNode = node; 15503 node = node.nextSibling; 15504 rootBlockNode.appendChild(tempNode); 15505 } else { 15506 rootBlockNode = null; 15507 node = node.nextSibling; 15508 } 15509 } 15510 15511 if (wrapped) { 15512 if (rng.setStart) { 15513 rng.setStart(startContainer, startOffset); 15514 rng.setEnd(endContainer, endOffset); 15515 selection.setRng(rng); 15516 } else { 15517 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 15518 if (isInEditorDocument) { 15519 try { 15520 rng = editor.getDoc().body.createTextRange(); 15521 rng.moveToElementText(rootNode); 15522 rng.collapse(true); 15523 rng.moveStart('character', startOffset); 15524 15525 if (endOffset > 0) 15526 rng.moveEnd('character', endOffset); 15527 15528 rng.select(); 15529 } catch (ex) { 15530 // Ignore 15531 } 15532 } 15533 } 15534 15535 editor.nodeChanged(); 15536 } 15537 }; 15538 15539 // Force root blocks 15540 if (settings.forced_root_block) { 15541 editor.onKeyUp.add(addRootBlocks); 15542 editor.onNodeChange.add(addRootBlocks); 15543 } 15544 }; 15545 15546 (function(tinymce) { 15547 // Shorten names 15548 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend; 15549 15550 tinymce.create('tinymce.ControlManager', { 15551 ControlManager : function(ed, s) { 15552 var t = this, i; 15553 15554 s = s || {}; 15555 t.editor = ed; 15556 t.controls = {}; 15557 t.onAdd = new tinymce.util.Dispatcher(t); 15558 t.onPostRender = new tinymce.util.Dispatcher(t); 15559 t.prefix = s.prefix || ed.id + '_'; 15560 t._cls = {}; 15561 15562 t.onPostRender.add(function() { 15563 each(t.controls, function(c) { 15564 c.postRender(); 15565 }); 15566 }); 15567 }, 15568 15569 get : function(id) { 15570 return this.controls[this.prefix + id] || this.controls[id]; 15571 }, 15572 15573 setActive : function(id, s) { 15574 var c = null; 15575 15576 if (c = this.get(id)) 15577 c.setActive(s); 15578 15579 return c; 15580 }, 15581 15582 setDisabled : function(id, s) { 15583 var c = null; 15584 15585 if (c = this.get(id)) 15586 c.setDisabled(s); 15587 15588 return c; 15589 }, 15590 15591 add : function(c) { 15592 var t = this; 15593 15594 if (c) { 15595 t.controls[c.id] = c; 15596 t.onAdd.dispatch(c, t); 15597 } 15598 15599 return c; 15600 }, 15601 15602 createControl : function(name) { 15603 var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName; 15604 15605 // Build control factory cache 15606 if (!self.controlFactories) { 15607 self.controlFactories = []; 15608 each(editor.plugins, function(plugin) { 15609 if (plugin.createControl) { 15610 self.controlFactories.push(plugin); 15611 } 15612 }); 15613 } 15614 15615 // Create controls by asking cached factories 15616 factories = self.controlFactories; 15617 for (i = 0, l = factories.length; i < l; i++) { 15618 ctrl = factories[i].createControl(name, self); 15619 15620 if (ctrl) { 15621 return self.add(ctrl); 15622 } 15623 } 15624 15625 // Create sepearator 15626 if (name === "|" || name === "separator") { 15627 return self.createSeparator(); 15628 } 15629 15630 // Create control from button collection 15631 if (editor.buttons && (ctrl = editor.buttons[name])) { 15632 return self.createButton(name, ctrl); 15633 } 15634 15635 return self.add(ctrl); 15636 }, 15637 15638 createDropMenu : function(id, s, cc) { 15639 var t = this, ed = t.editor, c, bm, v, cls; 15640 15641 s = extend({ 15642 'class' : 'mceDropDown', 15643 constrain : ed.settings.constrain_menus 15644 }, s); 15645 15646 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; 15647 if (v = ed.getParam('skin_variant')) 15648 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); 15649 15650 s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : ''; 15651 15652 id = t.prefix + id; 15653 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; 15654 c = t.controls[id] = new cls(id, s); 15655 c.onAddItem.add(function(c, o) { 15656 var s = o.settings; 15657 15658 s.title = ed.getLang(s.title, s.title); 15659 15660 if (!s.onclick) { 15661 s.onclick = function(v) { 15662 if (s.cmd) 15663 ed.execCommand(s.cmd, s.ui || false, s.value); 15664 }; 15665 } 15666 }); 15667 15668 ed.onRemove.add(function() { 15669 c.destroy(); 15670 }); 15671 15672 // Fix for bug #1897785, #1898007 15673 if (tinymce.isIE) { 15674 c.onShowMenu.add(function() { 15675 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15676 ed.focus(); 15677 15678 bm = ed.selection.getBookmark(1); 15679 }); 15680 15681 c.onHideMenu.add(function() { 15682 if (bm) { 15683 ed.selection.moveToBookmark(bm); 15684 bm = 0; 15685 } 15686 }); 15687 } 15688 15689 return t.add(c); 15690 }, 15691 15692 createListBox : function(id, s, cc) { 15693 var t = this, ed = t.editor, cmd, c, cls; 15694 15695 if (t.get(id)) 15696 return null; 15697 15698 s.title = ed.translate(s.title); 15699 s.scope = s.scope || ed; 15700 15701 if (!s.onselect) { 15702 s.onselect = function(v) { 15703 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15704 }; 15705 } 15706 15707 s = extend({ 15708 title : s.title, 15709 'class' : 'mce_' + id, 15710 scope : s.scope, 15711 control_manager : t 15712 }, s); 15713 15714 id = t.prefix + id; 15715 15716 15717 function useNativeListForAccessibility(ed) { 15718 return ed.settings.use_accessible_selects && !tinymce.isGecko 15719 } 15720 15721 if (ed.settings.use_native_selects || useNativeListForAccessibility(ed)) 15722 c = new tinymce.ui.NativeListBox(id, s); 15723 else { 15724 cls = cc || t._cls.listbox || tinymce.ui.ListBox; 15725 c = new cls(id, s, ed); 15726 } 15727 15728 t.controls[id] = c; 15729 15730 // Fix focus problem in Safari 15731 if (tinymce.isWebKit) { 15732 c.onPostRender.add(function(c, n) { 15733 // Store bookmark on mousedown 15734 Event.add(n, 'mousedown', function() { 15735 ed.bookmark = ed.selection.getBookmark(1); 15736 }); 15737 15738 // Restore on focus, since it might be lost 15739 Event.add(n, 'focus', function() { 15740 ed.selection.moveToBookmark(ed.bookmark); 15741 ed.bookmark = null; 15742 }); 15743 }); 15744 } 15745 15746 if (c.hideMenu) 15747 ed.onMouseDown.add(c.hideMenu, c); 15748 15749 return t.add(c); 15750 }, 15751 15752 createButton : function(id, s, cc) { 15753 var t = this, ed = t.editor, o, c, cls; 15754 15755 if (t.get(id)) 15756 return null; 15757 15758 s.title = ed.translate(s.title); 15759 s.label = ed.translate(s.label); 15760 s.scope = s.scope || ed; 15761 15762 if (!s.onclick && !s.menu_button) { 15763 s.onclick = function() { 15764 ed.execCommand(s.cmd, s.ui || false, s.value); 15765 }; 15766 } 15767 15768 s = extend({ 15769 title : s.title, 15770 'class' : 'mce_' + id, 15771 unavailable_prefix : ed.getLang('unavailable', ''), 15772 scope : s.scope, 15773 control_manager : t 15774 }, s); 15775 15776 id = t.prefix + id; 15777 15778 if (s.menu_button) { 15779 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; 15780 c = new cls(id, s, ed); 15781 ed.onMouseDown.add(c.hideMenu, c); 15782 } else { 15783 cls = t._cls.button || tinymce.ui.Button; 15784 c = new cls(id, s, ed); 15785 } 15786 15787 return t.add(c); 15788 }, 15789 15790 createMenuButton : function(id, s, cc) { 15791 s = s || {}; 15792 s.menu_button = 1; 15793 15794 return this.createButton(id, s, cc); 15795 }, 15796 15797 createSplitButton : function(id, s, cc) { 15798 var t = this, ed = t.editor, cmd, c, cls; 15799 15800 if (t.get(id)) 15801 return null; 15802 15803 s.title = ed.translate(s.title); 15804 s.scope = s.scope || ed; 15805 15806 if (!s.onclick) { 15807 s.onclick = function(v) { 15808 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15809 }; 15810 } 15811 15812 if (!s.onselect) { 15813 s.onselect = function(v) { 15814 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15815 }; 15816 } 15817 15818 s = extend({ 15819 title : s.title, 15820 'class' : 'mce_' + id, 15821 scope : s.scope, 15822 control_manager : t 15823 }, s); 15824 15825 id = t.prefix + id; 15826 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; 15827 c = t.add(new cls(id, s, ed)); 15828 ed.onMouseDown.add(c.hideMenu, c); 15829 15830 return c; 15831 }, 15832 15833 createColorSplitButton : function(id, s, cc) { 15834 var t = this, ed = t.editor, cmd, c, cls, bm; 15835 15836 if (t.get(id)) 15837 return null; 15838 15839 s.title = ed.translate(s.title); 15840 s.scope = s.scope || ed; 15841 15842 if (!s.onclick) { 15843 s.onclick = function(v) { 15844 if (tinymce.isIE) 15845 bm = ed.selection.getBookmark(1); 15846 15847 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15848 }; 15849 } 15850 15851 if (!s.onselect) { 15852 s.onselect = function(v) { 15853 ed.execCommand(s.cmd, s.ui || false, v || s.value); 15854 }; 15855 } 15856 15857 s = extend({ 15858 title : s.title, 15859 'class' : 'mce_' + id, 15860 'menu_class' : ed.getParam('skin') + 'Skin', 15861 scope : s.scope, 15862 more_colors_title : ed.getLang('more_colors') 15863 }, s); 15864 15865 id = t.prefix + id; 15866 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; 15867 c = new cls(id, s, ed); 15868 ed.onMouseDown.add(c.hideMenu, c); 15869 15870 // Remove the menu element when the editor is removed 15871 ed.onRemove.add(function() { 15872 c.destroy(); 15873 }); 15874 15875 // Fix for bug #1897785, #1898007 15876 if (tinymce.isIE) { 15877 c.onShowMenu.add(function() { 15878 // IE 8 needs focus in order to store away a range with the current collapsed caret location 15879 ed.focus(); 15880 bm = ed.selection.getBookmark(1); 15881 }); 15882 15883 c.onHideMenu.add(function() { 15884 if (bm) { 15885 ed.selection.moveToBookmark(bm); 15886 bm = 0; 15887 } 15888 }); 15889 } 15890 15891 return t.add(c); 15892 }, 15893 15894 createToolbar : function(id, s, cc) { 15895 var c, t = this, cls; 15896 15897 id = t.prefix + id; 15898 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; 15899 c = new cls(id, s, t.editor); 15900 15901 if (t.get(id)) 15902 return null; 15903 15904 return t.add(c); 15905 }, 15906 15907 createToolbarGroup : function(id, s, cc) { 15908 var c, t = this, cls; 15909 id = t.prefix + id; 15910 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; 15911 c = new cls(id, s, t.editor); 15912 15913 if (t.get(id)) 15914 return null; 15915 15916 return t.add(c); 15917 }, 15918 15919 createSeparator : function(cc) { 15920 var cls = cc || this._cls.separator || tinymce.ui.Separator; 15921 15922 return new cls(); 15923 }, 15924 15925 setControlType : function(n, c) { 15926 return this._cls[n.toLowerCase()] = c; 15927 }, 15928 15929 destroy : function() { 15930 each(this.controls, function(c) { 15931 c.destroy(); 15932 }); 15933 15934 this.controls = null; 15935 } 15936 }); 15937 })(tinymce); 15938 15939 (function(tinymce) { 15940 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera; 15941 15942 tinymce.create('tinymce.WindowManager', { 15943 WindowManager : function(ed) { 15944 var t = this; 15945 15946 t.editor = ed; 15947 t.onOpen = new Dispatcher(t); 15948 t.onClose = new Dispatcher(t); 15949 t.params = {}; 15950 t.features = {}; 15951 }, 15952 15953 open : function(s, p) { 15954 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u; 15955 15956 // Default some options 15957 s = s || {}; 15958 p = p || {}; 15959 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window 15960 sh = isOpera ? vp.h : screen.height; 15961 s.name = s.name || 'mc_' + new Date().getTime(); 15962 s.width = parseInt(s.width || 320); 15963 s.height = parseInt(s.height || 240); 15964 s.resizable = true; 15965 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0); 15966 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0); 15967 p.inline = false; 15968 p.mce_width = s.width; 15969 p.mce_height = s.height; 15970 p.mce_auto_focus = s.auto_focus; 15971 15972 if (mo) { 15973 if (isIE) { 15974 s.center = true; 15975 s.help = false; 15976 s.dialogWidth = s.width + 'px'; 15977 s.dialogHeight = s.height + 'px'; 15978 s.scroll = s.scrollbars || false; 15979 } 15980 } 15981 15982 // Build features string 15983 each(s, function(v, k) { 15984 if (tinymce.is(v, 'boolean')) 15985 v = v ? 'yes' : 'no'; 15986 15987 if (!/^(name|url)$/.test(k)) { 15988 if (isIE && mo) 15989 f += (f ? ';' : '') + k + ':' + v; 15990 else 15991 f += (f ? ',' : '') + k + '=' + v; 15992 } 15993 }); 15994 15995 t.features = s; 15996 t.params = p; 15997 t.onOpen.dispatch(t, s, p); 15998 15999 u = s.url || s.file; 16000 u = tinymce._addVer(u); 16001 16002 try { 16003 if (isIE && mo) { 16004 w = 1; 16005 window.showModalDialog(u, window, f); 16006 } else 16007 w = window.open(u, s.name, f); 16008 } catch (ex) { 16009 // Ignore 16010 } 16011 16012 if (!w) 16013 alert(t.editor.getLang('popup_blocked')); 16014 }, 16015 16016 close : function(w) { 16017 w.close(); 16018 this.onClose.dispatch(this); 16019 }, 16020 16021 createInstance : function(cl, a, b, c, d, e) { 16022 var f = tinymce.resolve(cl); 16023 16024 return new f(a, b, c, d, e); 16025 }, 16026 16027 confirm : function(t, cb, s, w) { 16028 w = w || window; 16029 16030 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t)))); 16031 }, 16032 16033 alert : function(tx, cb, s, w) { 16034 var t = this; 16035 16036 w = w || window; 16037 w.alert(t._decode(t.editor.getLang(tx, tx))); 16038 16039 if (cb) 16040 cb.call(s || t); 16041 }, 16042 16043 resizeBy : function(dw, dh, win) { 16044 win.resizeBy(dw, dh); 16045 }, 16046 16047 // Internal functions 16048 16049 _decode : function(s) { 16050 return tinymce.DOM.decode(s).replace(/\\n/g, '\n'); 16051 } 16052 }); 16053 }(tinymce)); 16054 (function(tinymce) { 16055 tinymce.Formatter = function(ed) { 16056 var formats = {}, 16057 each = tinymce.each, 16058 dom = ed.dom, 16059 selection = ed.selection, 16060 TreeWalker = tinymce.dom.TreeWalker, 16061 rangeUtils = new tinymce.dom.RangeUtils(dom), 16062 isValid = ed.schema.isValidChild, 16063 isBlock = dom.isBlock, 16064 forcedRootBlock = ed.settings.forced_root_block, 16065 nodeIndex = dom.nodeIndex, 16066 INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF', 16067 MCE_ATTR_RE = /^(src|href|style)$/, 16068 FALSE = false, 16069 TRUE = true, 16070 formatChangeData, 16071 undef, 16072 getContentEditable = dom.getContentEditable; 16073 16074 function isArray(obj) { 16075 return obj instanceof Array; 16076 }; 16077 16078 function getParents(node, selector) { 16079 return dom.getParents(node, selector, dom.getRoot()); 16080 }; 16081 16082 function isCaretNode(node) { 16083 return node.nodeType === 1 && node.id === '_mce_caret'; 16084 }; 16085 16086 function defaultFormats() { 16087 register({ 16088 alignleft : [ 16089 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'}, 16090 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} 16091 ], 16092 16093 aligncenter : [ 16094 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'}, 16095 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, 16096 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} 16097 ], 16098 16099 alignright : [ 16100 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'}, 16101 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} 16102 ], 16103 16104 alignfull : [ 16105 {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'} 16106 ], 16107 16108 bold : [ 16109 {inline : 'strong', remove : 'all'}, 16110 {inline : 'span', styles : {fontWeight : 'bold'}}, 16111 {inline : 'b', remove : 'all'} 16112 ], 16113 16114 italic : [ 16115 {inline : 'em', remove : 'all'}, 16116 {inline : 'span', styles : {fontStyle : 'italic'}}, 16117 {inline : 'i', remove : 'all'} 16118 ], 16119 16120 underline : [ 16121 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, 16122 {inline : 'u', remove : 'all'} 16123 ], 16124 16125 strikethrough : [ 16126 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, 16127 {inline : 'strike', remove : 'all'} 16128 ], 16129 16130 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, 16131 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, 16132 fontname : {inline : 'span', styles : {fontFamily : '%value'}}, 16133 fontsize : {inline : 'span', styles : {fontSize : '%value'}}, 16134 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, 16135 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, 16136 subscript : {inline : 'sub'}, 16137 superscript : {inline : 'sup'}, 16138 16139 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true, 16140 onmatch : function(node) { 16141 return true; 16142 }, 16143 16144 onformat : function(elm, fmt, vars) { 16145 each(vars, function(value, key) { 16146 dom.setAttrib(elm, key, value); 16147 }); 16148 } 16149 }, 16150 16151 removeformat : [ 16152 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, 16153 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true}, 16154 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true} 16155 ] 16156 }); 16157 16158 // Register default block formats 16159 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) { 16160 register(name, {block : name, remove : 'all'}); 16161 }); 16162 16163 // Register user defined formats 16164 register(ed.settings.formats); 16165 }; 16166 16167 function addKeyboardShortcuts() { 16168 // Add some inline shortcuts 16169 ed.addShortcut('ctrl+b', 'bold_desc', 'Bold'); 16170 ed.addShortcut('ctrl+i', 'italic_desc', 'Italic'); 16171 ed.addShortcut('ctrl+u', 'underline_desc', 'Underline'); 16172 16173 // BlockFormat shortcuts keys 16174 for (var i = 1; i <= 6; i++) { 16175 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]); 16176 } 16177 16178 ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']); 16179 ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']); 16180 ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']); 16181 }; 16182 16183 // Public functions 16184 16185 function get(name) { 16186 return name ? formats[name] : formats; 16187 }; 16188 16189 function register(name, format) { 16190 if (name) { 16191 if (typeof(name) !== 'string') { 16192 each(name, function(format, name) { 16193 register(name, format); 16194 }); 16195 } else { 16196 // Force format into array and add it to internal collection 16197 format = format.length ? format : [format]; 16198 16199 each(format, function(format) { 16200 // Set deep to false by default on selector formats this to avoid removing 16201 // alignment on images inside paragraphs when alignment is changed on paragraphs 16202 if (format.deep === undef) 16203 format.deep = !format.selector; 16204 16205 // Default to true 16206 if (format.split === undef) 16207 format.split = !format.selector || format.inline; 16208 16209 // Default to true 16210 if (format.remove === undef && format.selector && !format.inline) 16211 format.remove = 'none'; 16212 16213 // Mark format as a mixed format inline + block level 16214 if (format.selector && format.inline) { 16215 format.mixed = true; 16216 format.block_expand = true; 16217 } 16218 16219 // Split classes if needed 16220 if (typeof(format.classes) === 'string') 16221 format.classes = format.classes.split(/\s+/); 16222 }); 16223 16224 formats[name] = format; 16225 } 16226 } 16227 }; 16228 16229 var getTextDecoration = function(node) { 16230 var decoration; 16231 16232 ed.dom.getParent(node, function(n) { 16233 decoration = ed.dom.getStyle(n, 'text-decoration'); 16234 return decoration && decoration !== 'none'; 16235 }); 16236 16237 return decoration; 16238 }; 16239 16240 var processUnderlineAndColor = function(node) { 16241 var textDecoration; 16242 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 16243 textDecoration = getTextDecoration(node.parentNode); 16244 if (ed.dom.getStyle(node, 'color') && textDecoration) { 16245 ed.dom.setStyle(node, 'text-decoration', textDecoration); 16246 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { 16247 ed.dom.setStyle(node, 'text-decoration', null); 16248 } 16249 } 16250 }; 16251 16252 function apply(name, vars, node) { 16253 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); 16254 16255 function setElementFormat(elm, fmt) { 16256 fmt = fmt || format; 16257 16258 if (elm) { 16259 if (fmt.onformat) { 16260 fmt.onformat(elm, fmt, vars, node); 16261 } 16262 16263 each(fmt.styles, function(value, name) { 16264 dom.setStyle(elm, name, replaceVars(value, vars)); 16265 }); 16266 16267 each(fmt.attributes, function(value, name) { 16268 dom.setAttrib(elm, name, replaceVars(value, vars)); 16269 }); 16270 16271 each(fmt.classes, function(value) { 16272 value = replaceVars(value, vars); 16273 16274 if (!dom.hasClass(elm, value)) 16275 dom.addClass(elm, value); 16276 }); 16277 } 16278 }; 16279 function adjustSelectionToVisibleSelection() { 16280 function findSelectionEnd(start, end) { 16281 var walker = new TreeWalker(end); 16282 for (node = walker.current(); node; node = walker.prev()) { 16283 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 16284 return node; 16285 } 16286 } 16287 }; 16288 16289 // Adjust selection so that a end container with a end offset of zero is not included in the selection 16290 // as this isn't visible to the user. 16291 var rng = ed.selection.getRng(); 16292 var start = rng.startContainer; 16293 var end = rng.endContainer; 16294 16295 if (start != end && rng.endOffset === 0) { 16296 var newEnd = findSelectionEnd(start, end); 16297 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 16298 16299 rng.setEnd(newEnd, endOffset); 16300 } 16301 16302 return rng; 16303 } 16304 16305 function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){ 16306 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm; 16307 16308 // find the index of the first child list. 16309 each(node.childNodes, function(n, index) { 16310 if (n.nodeName === "UL" || n.nodeName === "OL") { 16311 listIndex = index; 16312 list = n; 16313 return false; 16314 } 16315 }); 16316 16317 // get the index of the bookmarks 16318 each(node.childNodes, function(n, index) { 16319 if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") { 16320 if (n.id == bookmark.id + "_start") { 16321 startIndex = index; 16322 } else if (n.id == bookmark.id + "_end") { 16323 endIndex = index; 16324 } 16325 } 16326 }); 16327 16328 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally 16329 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) { 16330 each(tinymce.grep(node.childNodes), process); 16331 return 0; 16332 } else { 16333 currentWrapElm = dom.clone(wrapElm, FALSE); 16334 16335 // create a list of the nodes on the same side of the list as the selection 16336 each(tinymce.grep(node.childNodes), function(n, index) { 16337 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) { 16338 nodes.push(n); 16339 n.parentNode.removeChild(n); 16340 } 16341 }); 16342 16343 // insert the wrapping element either before or after the list. 16344 if (startIndex < listIndex) { 16345 node.insertBefore(currentWrapElm, list); 16346 } else if (startIndex > listIndex) { 16347 node.insertBefore(currentWrapElm, list.nextSibling); 16348 } 16349 16350 // add the new nodes to the list. 16351 newWrappers.push(currentWrapElm); 16352 16353 each(nodes, function(node) { 16354 currentWrapElm.appendChild(node); 16355 }); 16356 16357 return currentWrapElm; 16358 } 16359 }; 16360 16361 function applyRngStyle(rng, bookmark, node_specific) { 16362 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 16363 16364 // Setup wrapper element 16365 wrapName = format.inline || format.block; 16366 wrapElm = dom.create(wrapName); 16367 setElementFormat(wrapElm); 16368 16369 rangeUtils.walk(rng, function(nodes) { 16370 var currentWrapElm; 16371 16372 function process(node) { 16373 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 16374 16375 lastContentEditable = contentEditable; 16376 nodeName = node.nodeName.toLowerCase(); 16377 parentName = node.parentNode.nodeName.toLowerCase(); 16378 16379 // Node has a contentEditable value 16380 if (node.nodeType === 1 && getContentEditable(node)) { 16381 lastContentEditable = contentEditable; 16382 contentEditable = getContentEditable(node) === "true"; 16383 hasContentEditableState = true; // We don't want to wrap the container only it's children 16384 } 16385 16386 // Stop wrapping on br elements 16387 if (isEq(nodeName, 'br')) { 16388 currentWrapElm = 0; 16389 16390 // Remove any br elements when we wrap things 16391 if (format.block) 16392 dom.remove(node); 16393 16394 return; 16395 } 16396 16397 // If node is wrapper type 16398 if (format.wrapper && matchNode(node, name, vars)) { 16399 currentWrapElm = 0; 16400 return; 16401 } 16402 16403 // Can we rename the block 16404 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) { 16405 node = dom.rename(node, wrapName); 16406 setElementFormat(node); 16407 newWrappers.push(node); 16408 currentWrapElm = 0; 16409 return; 16410 } 16411 16412 // Handle selector patterns 16413 if (format.selector) { 16414 // Look for matching formats 16415 each(formatList, function(format) { 16416 // Check collapsed state if it exists 16417 if ('collapsed' in format && format.collapsed !== isCollapsed) { 16418 return; 16419 } 16420 16421 if (dom.is(node, format.selector) && !isCaretNode(node)) { 16422 setElementFormat(node, format); 16423 found = true; 16424 } 16425 }); 16426 16427 // Continue processing if a selector match wasn't found and a inline element is defined 16428 if (!format.inline || found) { 16429 currentWrapElm = 0; 16430 return; 16431 } 16432 } 16433 16434 // Is it valid to wrap this item 16435 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 16436 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) { 16437 // Start wrapping 16438 if (!currentWrapElm) { 16439 // Wrap the node 16440 currentWrapElm = dom.clone(wrapElm, FALSE); 16441 node.parentNode.insertBefore(currentWrapElm, node); 16442 newWrappers.push(currentWrapElm); 16443 } 16444 16445 currentWrapElm.appendChild(node); 16446 } else if (nodeName == 'li' && bookmark) { 16447 // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element. 16448 currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process); 16449 } else { 16450 // Start a new wrapper for possible children 16451 currentWrapElm = 0; 16452 16453 each(tinymce.grep(node.childNodes), process); 16454 16455 if (hasContentEditableState) { 16456 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16457 } 16458 16459 // End the last wrapper 16460 currentWrapElm = 0; 16461 } 16462 }; 16463 16464 // Process siblings from range 16465 each(nodes, process); 16466 }); 16467 16468 // Wrap links inside as well, for example color inside a link when the wrapper is around the link 16469 if (format.wrap_links === false) { 16470 each(newWrappers, function(node) { 16471 function process(node) { 16472 var i, currentWrapElm, children; 16473 16474 if (node.nodeName === 'A') { 16475 currentWrapElm = dom.clone(wrapElm, FALSE); 16476 newWrappers.push(currentWrapElm); 16477 16478 children = tinymce.grep(node.childNodes); 16479 for (i = 0; i < children.length; i++) 16480 currentWrapElm.appendChild(children[i]); 16481 16482 node.appendChild(currentWrapElm); 16483 } 16484 16485 each(tinymce.grep(node.childNodes), process); 16486 }; 16487 16488 process(node); 16489 }); 16490 } 16491 16492 // Cleanup 16493 16494 each(newWrappers, function(node) { 16495 var childCount; 16496 16497 function getChildCount(node) { 16498 var count = 0; 16499 16500 each(node.childNodes, function(node) { 16501 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) 16502 count++; 16503 }); 16504 16505 return count; 16506 }; 16507 16508 function mergeStyles(node) { 16509 var child, clone; 16510 16511 each(node.childNodes, function(node) { 16512 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 16513 child = node; 16514 return FALSE; // break loop 16515 } 16516 }); 16517 16518 // If child was found and of the same type as the current node 16519 if (child && matchName(child, format)) { 16520 clone = dom.clone(child, FALSE); 16521 setElementFormat(clone); 16522 16523 dom.replace(clone, node, TRUE); 16524 dom.remove(child, 1); 16525 } 16526 16527 return clone || node; 16528 }; 16529 16530 childCount = getChildCount(node); 16531 16532 // Remove empty nodes but only if there is multiple wrappers and they are not block 16533 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at 16534 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 16535 dom.remove(node, 1); 16536 return; 16537 } 16538 16539 if (format.inline || format.wrapper) { 16540 // Merges the current node with it's children of similar type to reduce the number of elements 16541 if (!format.exact && childCount === 1) 16542 node = mergeStyles(node); 16543 16544 // Remove/merge children 16545 each(formatList, function(format) { 16546 // Merge all children of similar type will move styles from child to parent 16547 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 16548 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 16549 each(dom.select(format.inline, node), function(child) { 16550 var parent; 16551 16552 // When wrap_links is set to false we don't want 16553 // to remove the format on children within links 16554 if (format.wrap_links === false) { 16555 parent = child.parentNode; 16556 16557 do { 16558 if (parent.nodeName === 'A') 16559 return; 16560 } while (parent = parent.parentNode); 16561 } 16562 16563 removeFormat(format, vars, child, format.exact ? child : null); 16564 }); 16565 }); 16566 16567 // Remove child if direct parent is of same type 16568 if (matchNode(node.parentNode, name, vars)) { 16569 dom.remove(node, 1); 16570 node = 0; 16571 return TRUE; 16572 } 16573 16574 // Look for parent with similar style format 16575 if (format.merge_with_parents) { 16576 dom.getParent(node.parentNode, function(parent) { 16577 if (matchNode(parent, name, vars)) { 16578 dom.remove(node, 1); 16579 node = 0; 16580 return TRUE; 16581 } 16582 }); 16583 } 16584 16585 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 16586 if (node && format.merge_siblings !== false) { 16587 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 16588 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 16589 } 16590 } 16591 }); 16592 }; 16593 16594 if (format) { 16595 if (node) { 16596 if (node.nodeType) { 16597 rng = dom.createRng(); 16598 rng.setStartBefore(node); 16599 rng.setEndAfter(node); 16600 applyRngStyle(expandRng(rng, formatList), null, true); 16601 } else { 16602 applyRngStyle(node, null, true); 16603 } 16604 } else { 16605 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16606 // Obtain selection node before selection is unselected by applyRngStyle() 16607 var curSelNode = ed.selection.getNode(); 16608 16609 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false 16610 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 16611 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 16612 apply(formatList[0].defaultBlock); 16613 } 16614 16615 // Apply formatting to selection 16616 ed.selection.setRng(adjustSelectionToVisibleSelection()); 16617 bookmark = selection.getBookmark(); 16618 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 16619 16620 // Colored nodes should be underlined so that the color of the underline matches the text color. 16621 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 16622 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); 16623 processUnderlineAndColor(curSelNode); 16624 } 16625 16626 selection.moveToBookmark(bookmark); 16627 moveStart(selection.getRng(TRUE)); 16628 ed.nodeChanged(); 16629 } else 16630 performCaretAction('apply', name, vars); 16631 } 16632 } 16633 }; 16634 16635 function remove(name, vars, node) { 16636 var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true; 16637 16638 // Merges the styles for each node 16639 function process(node) { 16640 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState; 16641 16642 // Node has a contentEditable value 16643 if (node.nodeType === 1 && getContentEditable(node)) { 16644 lastContentEditable = contentEditable; 16645 contentEditable = getContentEditable(node) === "true"; 16646 hasContentEditableState = true; // We don't want to wrap the container only it's children 16647 } 16648 16649 // Grab the children first since the nodelist might be changed 16650 children = tinymce.grep(node.childNodes); 16651 16652 // Process current node 16653 if (contentEditable && !hasContentEditableState) { 16654 for (i = 0, l = formatList.length; i < l; i++) { 16655 if (removeFormat(formatList[i], vars, node, node)) 16656 break; 16657 } 16658 } 16659 16660 // Process the children 16661 if (format.deep) { 16662 if (children.length) { 16663 for (i = 0, l = children.length; i < l; i++) 16664 process(children[i]); 16665 16666 if (hasContentEditableState) { 16667 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 16668 } 16669 } 16670 } 16671 }; 16672 16673 function findFormatRoot(container) { 16674 var formatRoot; 16675 16676 // Find format root 16677 each(getParents(container.parentNode).reverse(), function(parent) { 16678 var format; 16679 16680 // Find format root element 16681 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 16682 // Is the node matching the format we are looking for 16683 format = matchNode(parent, name, vars); 16684 if (format && format.split !== false) 16685 formatRoot = parent; 16686 } 16687 }); 16688 16689 return formatRoot; 16690 }; 16691 16692 function wrapAndSplit(format_root, container, target, split) { 16693 var parent, clone, lastClone, firstClone, i, formatRootParent; 16694 16695 // Format root found then clone formats and split it 16696 if (format_root) { 16697 formatRootParent = format_root.parentNode; 16698 16699 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 16700 clone = dom.clone(parent, FALSE); 16701 16702 for (i = 0; i < formatList.length; i++) { 16703 if (removeFormat(formatList[i], vars, clone, clone)) { 16704 clone = 0; 16705 break; 16706 } 16707 } 16708 16709 // Build wrapper node 16710 if (clone) { 16711 if (lastClone) 16712 clone.appendChild(lastClone); 16713 16714 if (!firstClone) 16715 firstClone = clone; 16716 16717 lastClone = clone; 16718 } 16719 } 16720 16721 // Never split block elements if the format is mixed 16722 if (split && (!format.mixed || !isBlock(format_root))) 16723 container = dom.split(format_root, container); 16724 16725 // Wrap container in cloned formats 16726 if (lastClone) { 16727 target.parentNode.insertBefore(lastClone, target); 16728 firstClone.appendChild(target); 16729 } 16730 } 16731 16732 return container; 16733 }; 16734 16735 function splitToFormatRoot(container) { 16736 return wrapAndSplit(findFormatRoot(container), container, container, true); 16737 }; 16738 16739 function unwrap(start) { 16740 var node = dom.get(start ? '_start' : '_end'), 16741 out = node[start ? 'firstChild' : 'lastChild']; 16742 16743 // If the end is placed within the start the result will be removed 16744 // So this checks if the out node is a bookmark node if it is it 16745 // checks for another more suitable node 16746 if (isBookmarkNode(out)) 16747 out = out[start ? 'firstChild' : 'lastChild']; 16748 16749 dom.remove(node, true); 16750 16751 return out; 16752 }; 16753 16754 function removeRngStyle(rng) { 16755 var startContainer, endContainer, node; 16756 16757 rng = expandRng(rng, formatList, TRUE); 16758 16759 if (format.split) { 16760 startContainer = getContainer(rng, TRUE); 16761 endContainer = getContainer(rng); 16762 16763 if (startContainer != endContainer) { 16764 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead 16765 // This will happen if you tripple click a table cell and use remove formatting 16766 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 16767 startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer; 16768 } 16769 16770 // Wrap start/end nodes in span element since these might be cloned/moved 16771 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); 16772 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); 16773 16774 // Split start/end 16775 splitToFormatRoot(startContainer); 16776 splitToFormatRoot(endContainer); 16777 16778 // Unwrap start/end to get real elements again 16779 startContainer = unwrap(TRUE); 16780 endContainer = unwrap(); 16781 } else 16782 startContainer = endContainer = splitToFormatRoot(startContainer); 16783 16784 // Update range positions since they might have changed after the split operations 16785 rng.startContainer = startContainer.parentNode; 16786 rng.startOffset = nodeIndex(startContainer); 16787 rng.endContainer = endContainer.parentNode; 16788 rng.endOffset = nodeIndex(endContainer) + 1; 16789 } 16790 16791 // Remove items between start/end 16792 rangeUtils.walk(rng, function(nodes) { 16793 each(nodes, function(node) { 16794 process(node); 16795 16796 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 16797 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 16798 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); 16799 } 16800 }); 16801 }); 16802 }; 16803 16804 // Handle node 16805 if (node) { 16806 if (node.nodeType) { 16807 rng = dom.createRng(); 16808 rng.setStartBefore(node); 16809 rng.setEndAfter(node); 16810 removeRngStyle(rng); 16811 } else { 16812 removeRngStyle(node); 16813 } 16814 16815 return; 16816 } 16817 16818 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { 16819 bookmark = selection.getBookmark(); 16820 removeRngStyle(selection.getRng(TRUE)); 16821 selection.moveToBookmark(bookmark); 16822 16823 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node 16824 if (format.inline && match(name, vars, selection.getStart())) { 16825 moveStart(selection.getRng(true)); 16826 } 16827 16828 ed.nodeChanged(); 16829 } else 16830 performCaretAction('remove', name, vars); 16831 }; 16832 16833 function toggle(name, vars, node) { 16834 var fmt = get(name); 16835 16836 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) 16837 remove(name, vars, node); 16838 else 16839 apply(name, vars, node); 16840 }; 16841 16842 function matchNode(node, name, vars, similar) { 16843 var formatList = get(name), format, i, classes; 16844 16845 function matchItems(node, format, item_name) { 16846 var key, value, items = format[item_name], i; 16847 16848 // Custom match 16849 if (format.onmatch) { 16850 return format.onmatch(node, format, item_name); 16851 } 16852 16853 // Check all items 16854 if (items) { 16855 // Non indexed object 16856 if (items.length === undef) { 16857 for (key in items) { 16858 if (items.hasOwnProperty(key)) { 16859 if (item_name === 'attributes') 16860 value = dom.getAttrib(node, key); 16861 else 16862 value = getStyle(node, key); 16863 16864 if (similar && !value && !format.exact) 16865 return; 16866 16867 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars))) 16868 return; 16869 } 16870 } 16871 } else { 16872 // Only one match needed for indexed arrays 16873 for (i = 0; i < items.length; i++) { 16874 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) 16875 return format; 16876 } 16877 } 16878 } 16879 16880 return format; 16881 }; 16882 16883 if (formatList && node) { 16884 // Check each format in list 16885 for (i = 0; i < formatList.length; i++) { 16886 format = formatList[i]; 16887 16888 // Name name, attributes, styles and classes 16889 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 16890 // Match classes 16891 if (classes = format.classes) { 16892 for (i = 0; i < classes.length; i++) { 16893 if (!dom.hasClass(node, classes[i])) 16894 return; 16895 } 16896 } 16897 16898 return format; 16899 } 16900 } 16901 } 16902 }; 16903 16904 function match(name, vars, node) { 16905 var startNode; 16906 16907 function matchParents(node) { 16908 // Find first node with similar format settings 16909 node = dom.getParent(node, function(node) { 16910 return !!matchNode(node, name, vars, true); 16911 }); 16912 16913 // Do an exact check on the similar format element 16914 return matchNode(node, name, vars); 16915 }; 16916 16917 // Check specified node 16918 if (node) 16919 return matchParents(node); 16920 16921 // Check selected node 16922 node = selection.getNode(); 16923 if (matchParents(node)) 16924 return TRUE; 16925 16926 // Check start node if it's different 16927 startNode = selection.getStart(); 16928 if (startNode != node) { 16929 if (matchParents(startNode)) 16930 return TRUE; 16931 } 16932 16933 return FALSE; 16934 }; 16935 16936 function matchAll(names, vars) { 16937 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name; 16938 16939 // Check start of selection for formats 16940 startElement = selection.getStart(); 16941 dom.getParent(startElement, function(node) { 16942 var i, name; 16943 16944 for (i = 0; i < names.length; i++) { 16945 name = names[i]; 16946 16947 if (!checkedMap[name] && matchNode(node, name, vars)) { 16948 checkedMap[name] = true; 16949 matchedFormatNames.push(name); 16950 } 16951 } 16952 }, dom.getRoot()); 16953 16954 return matchedFormatNames; 16955 }; 16956 16957 function canApply(name) { 16958 var formatList = get(name), startNode, parents, i, x, selector; 16959 16960 if (formatList) { 16961 startNode = selection.getStart(); 16962 parents = getParents(startNode); 16963 16964 for (x = formatList.length - 1; x >= 0; x--) { 16965 selector = formatList[x].selector; 16966 16967 // Format is not selector based, then always return TRUE 16968 if (!selector) 16969 return TRUE; 16970 16971 for (i = parents.length - 1; i >= 0; i--) { 16972 if (dom.is(parents[i], selector)) 16973 return TRUE; 16974 } 16975 } 16976 } 16977 16978 return FALSE; 16979 }; 16980 16981 function formatChanged(formats, callback) { 16982 var currentFormats; 16983 16984 // Setup format node change logic 16985 if (!formatChangeData) { 16986 formatChangeData = {}; 16987 currentFormats = {}; 16988 16989 ed.onNodeChange.addToTop(function(ed, cm, node) { 16990 var parents = getParents(node), matchedFormats = {}; 16991 16992 // Check for new formats 16993 each(formatChangeData, function(callbacks, format) { 16994 each(parents, function(node) { 16995 if (matchNode(node, format, {}, true)) { 16996 if (!currentFormats[format]) { 16997 // Execute callbacks 16998 each(callbacks, function(callback) { 16999 callback(true, {node: node, format: format, parents: parents}); 17000 }); 17001 17002 currentFormats[format] = callbacks; 17003 } 17004 17005 matchedFormats[format] = callbacks; 17006 return false; 17007 } 17008 }); 17009 }); 17010 17011 // Check if current formats still match 17012 each(currentFormats, function(callbacks, format) { 17013 if (!matchedFormats[format]) { 17014 delete currentFormats[format]; 17015 17016 each(callbacks, function(callback) { 17017 callback(false, {node: node, format: format, parents: parents}); 17018 }); 17019 } 17020 }); 17021 }); 17022 } 17023 17024 // Add format listeners 17025 each(formats.split(','), function(format) { 17026 if (!formatChangeData[format]) { 17027 formatChangeData[format] = []; 17028 } 17029 17030 formatChangeData[format].push(callback); 17031 }); 17032 17033 return this; 17034 }; 17035 17036 // Expose to public 17037 tinymce.extend(this, { 17038 get : get, 17039 register : register, 17040 apply : apply, 17041 remove : remove, 17042 toggle : toggle, 17043 match : match, 17044 matchAll : matchAll, 17045 matchNode : matchNode, 17046 canApply : canApply, 17047 formatChanged: formatChanged 17048 }); 17049 17050 // Initialize 17051 defaultFormats(); 17052 addKeyboardShortcuts(); 17053 17054 // Private functions 17055 17056 function matchName(node, format) { 17057 // Check for inline match 17058 if (isEq(node, format.inline)) 17059 return TRUE; 17060 17061 // Check for block match 17062 if (isEq(node, format.block)) 17063 return TRUE; 17064 17065 // Check for selector match 17066 if (format.selector) 17067 return dom.is(node, format.selector); 17068 }; 17069 17070 function isEq(str1, str2) { 17071 str1 = str1 || ''; 17072 str2 = str2 || ''; 17073 17074 str1 = '' + (str1.nodeName || str1); 17075 str2 = '' + (str2.nodeName || str2); 17076 17077 return str1.toLowerCase() == str2.toLowerCase(); 17078 }; 17079 17080 function getStyle(node, name) { 17081 var styleVal = dom.getStyle(node, name); 17082 17083 // Force the format to hex 17084 if (name == 'color' || name == 'backgroundColor') 17085 styleVal = dom.toHex(styleVal); 17086 17087 // Opera will return bold as 700 17088 if (name == 'fontWeight' && styleVal == 700) 17089 styleVal = 'bold'; 17090 17091 return '' + styleVal; 17092 }; 17093 17094 function replaceVars(value, vars) { 17095 if (typeof(value) != "string") 17096 value = value(vars); 17097 else if (vars) { 17098 value = value.replace(/%(\w+)/g, function(str, name) { 17099 return vars[name] || str; 17100 }); 17101 } 17102 17103 return value; 17104 }; 17105 17106 function isWhiteSpaceNode(node) { 17107 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 17108 }; 17109 17110 function wrap(node, name, attrs) { 17111 var wrapper = dom.create(name, attrs); 17112 17113 node.parentNode.insertBefore(wrapper, node); 17114 wrapper.appendChild(node); 17115 17116 return wrapper; 17117 }; 17118 17119 function expandRng(rng, format, remove) { 17120 var sibling, lastIdx, leaf, endPoint, 17121 startContainer = rng.startContainer, 17122 startOffset = rng.startOffset, 17123 endContainer = rng.endContainer, 17124 endOffset = rng.endOffset; 17125 17126 // This function walks up the tree if there is no siblings before/after the node 17127 function findParentContainer(start) { 17128 var container, parent, child, sibling, siblingName, root; 17129 17130 container = parent = start ? startContainer : endContainer; 17131 siblingName = start ? 'previousSibling' : 'nextSibling'; 17132 root = dom.getRoot(); 17133 17134 // If it's a text node and the offset is inside the text 17135 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 17136 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 17137 return container; 17138 } 17139 } 17140 17141 for (;;) { 17142 // Stop expanding on block elements 17143 if (!format[0].block_expand && isBlock(parent)) 17144 return parent; 17145 17146 // Walk left/right 17147 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 17148 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) { 17149 return parent; 17150 } 17151 } 17152 17153 // Check if we can move up are we at root level or body level 17154 if (parent.parentNode == root) { 17155 container = parent; 17156 break; 17157 } 17158 17159 parent = parent.parentNode; 17160 } 17161 17162 return container; 17163 }; 17164 17165 // This function walks down the tree to find the leaf at the selection. 17166 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 17167 function findLeaf(node, offset) { 17168 if (offset === undef) 17169 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17170 while (node && node.hasChildNodes()) { 17171 node = node.childNodes[offset]; 17172 if (node) 17173 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 17174 } 17175 return { node: node, offset: offset }; 17176 } 17177 17178 // If index based start position then resolve it 17179 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 17180 lastIdx = startContainer.childNodes.length - 1; 17181 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 17182 17183 if (startContainer.nodeType == 3) 17184 startOffset = 0; 17185 } 17186 17187 // If index based end position then resolve it 17188 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 17189 lastIdx = endContainer.childNodes.length - 1; 17190 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 17191 17192 if (endContainer.nodeType == 3) 17193 endOffset = endContainer.nodeValue.length; 17194 } 17195 17196 // Expands the node to the closes contentEditable false element if it exists 17197 function findParentContentEditable(node) { 17198 var parent = node; 17199 17200 while (parent) { 17201 if (parent.nodeType === 1 && getContentEditable(parent)) { 17202 return getContentEditable(parent) === "false" ? parent : node; 17203 } 17204 17205 parent = parent.parentNode; 17206 } 17207 17208 return node; 17209 }; 17210 17211 function findWordEndPoint(container, offset, start) { 17212 var walker, node, pos, lastTextNode; 17213 17214 function findSpace(node, offset) { 17215 var pos, pos2, str = node.nodeValue; 17216 17217 if (typeof(offset) == "undefined") { 17218 offset = start ? str.length : 0; 17219 } 17220 17221 if (start) { 17222 pos = str.lastIndexOf(' ', offset); 17223 pos2 = str.lastIndexOf('\u00a0', offset); 17224 pos = pos > pos2 ? pos : pos2; 17225 17226 // Include the space on remove to avoid tag soup 17227 if (pos !== -1 && !remove) { 17228 pos++; 17229 } 17230 } else { 17231 pos = str.indexOf(' ', offset); 17232 pos2 = str.indexOf('\u00a0', offset); 17233 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 17234 } 17235 17236 return pos; 17237 }; 17238 17239 if (container.nodeType === 3) { 17240 pos = findSpace(container, offset); 17241 17242 if (pos !== -1) { 17243 return {container : container, offset : pos}; 17244 } 17245 17246 lastTextNode = container; 17247 } 17248 17249 // Walk the nodes inside the block 17250 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 17251 while (node = walker[start ? 'prev' : 'next']()) { 17252 if (node.nodeType === 3) { 17253 lastTextNode = node; 17254 pos = findSpace(node); 17255 17256 if (pos !== -1) { 17257 return {container : node, offset : pos}; 17258 } 17259 } else if (isBlock(node)) { 17260 break; 17261 } 17262 } 17263 17264 if (lastTextNode) { 17265 if (start) { 17266 offset = 0; 17267 } else { 17268 offset = lastTextNode.length; 17269 } 17270 17271 return {container: lastTextNode, offset: offset}; 17272 } 17273 }; 17274 17275 function findSelectorEndPoint(container, sibling_name) { 17276 var parents, i, y, curFormat; 17277 17278 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) 17279 container = container[sibling_name]; 17280 17281 parents = getParents(container); 17282 for (i = 0; i < parents.length; i++) { 17283 for (y = 0; y < format.length; y++) { 17284 curFormat = format[y]; 17285 17286 // If collapsed state is set then skip formats that doesn't match that 17287 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) 17288 continue; 17289 17290 if (dom.is(parents[i], curFormat.selector)) 17291 return parents[i]; 17292 } 17293 } 17294 17295 return container; 17296 }; 17297 17298 function findBlockEndPoint(container, sibling_name, sibling_name2) { 17299 var node; 17300 17301 // Expand to block of similar type 17302 if (!format[0].wrapper) 17303 node = dom.getParent(container, format[0].block); 17304 17305 // Expand to first wrappable block element or any block element 17306 if (!node) 17307 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock); 17308 17309 // Exclude inner lists from wrapping 17310 if (node && format[0].wrapper) 17311 node = getParents(node, 'ul,ol').reverse()[0] || node; 17312 17313 // Didn't find a block element look for first/last wrappable element 17314 if (!node) { 17315 node = container; 17316 17317 while (node[sibling_name] && !isBlock(node[sibling_name])) { 17318 node = node[sibling_name]; 17319 17320 // Break on BR but include it will be removed later on 17321 // we can't remove it now since we need to check if it can be wrapped 17322 if (isEq(node, 'br')) 17323 break; 17324 } 17325 } 17326 17327 return node || container; 17328 }; 17329 17330 // Expand to closest contentEditable element 17331 startContainer = findParentContentEditable(startContainer); 17332 endContainer = findParentContentEditable(endContainer); 17333 17334 // Exclude bookmark nodes if possible 17335 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 17336 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 17337 startContainer = startContainer.nextSibling || startContainer; 17338 17339 if (startContainer.nodeType == 3) 17340 startOffset = 0; 17341 } 17342 17343 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 17344 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 17345 endContainer = endContainer.previousSibling || endContainer; 17346 17347 if (endContainer.nodeType == 3) 17348 endOffset = endContainer.length; 17349 } 17350 17351 if (format[0].inline) { 17352 if (rng.collapsed) { 17353 // Expand left to closest word boundery 17354 endPoint = findWordEndPoint(startContainer, startOffset, true); 17355 if (endPoint) { 17356 startContainer = endPoint.container; 17357 startOffset = endPoint.offset; 17358 } 17359 17360 // Expand right to closest word boundery 17361 endPoint = findWordEndPoint(endContainer, endOffset); 17362 if (endPoint) { 17363 endContainer = endPoint.container; 17364 endOffset = endPoint.offset; 17365 } 17366 } 17367 17368 // Avoid applying formatting to a trailing space. 17369 leaf = findLeaf(endContainer, endOffset); 17370 if (leaf.node) { 17371 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) 17372 leaf = findLeaf(leaf.node.previousSibling); 17373 17374 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 17375 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 17376 17377 if (leaf.offset > 1) { 17378 endContainer = leaf.node; 17379 endContainer.splitText(leaf.offset - 1); 17380 } 17381 } 17382 } 17383 } 17384 17385 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 17386 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 17387 // This will reduce the number of wrapper elements that needs to be created 17388 // Move start point up the tree 17389 if (format[0].inline || format[0].block_expand) { 17390 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 17391 startContainer = findParentContainer(true); 17392 } 17393 17394 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 17395 endContainer = findParentContainer(); 17396 } 17397 } 17398 17399 // Expand start/end container to matching selector 17400 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 17401 // Find new startContainer/endContainer if there is better one 17402 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 17403 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 17404 } 17405 17406 // Expand start/end container to matching block element or text node 17407 if (format[0].block || format[0].selector) { 17408 // Find new startContainer/endContainer if there is better one 17409 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 17410 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 17411 17412 // Non block element then try to expand up the leaf 17413 if (format[0].block) { 17414 if (!isBlock(startContainer)) 17415 startContainer = findParentContainer(true); 17416 17417 if (!isBlock(endContainer)) 17418 endContainer = findParentContainer(); 17419 } 17420 } 17421 17422 // Setup index for startContainer 17423 if (startContainer.nodeType == 1) { 17424 startOffset = nodeIndex(startContainer); 17425 startContainer = startContainer.parentNode; 17426 } 17427 17428 // Setup index for endContainer 17429 if (endContainer.nodeType == 1) { 17430 endOffset = nodeIndex(endContainer) + 1; 17431 endContainer = endContainer.parentNode; 17432 } 17433 17434 // Return new range like object 17435 return { 17436 startContainer : startContainer, 17437 startOffset : startOffset, 17438 endContainer : endContainer, 17439 endOffset : endOffset 17440 }; 17441 } 17442 17443 function removeFormat(format, vars, node, compare_node) { 17444 var i, attrs, stylesModified; 17445 17446 // Check if node matches format 17447 if (!matchName(node, format)) 17448 return FALSE; 17449 17450 // Should we compare with format attribs and styles 17451 if (format.remove != 'all') { 17452 // Remove styles 17453 each(format.styles, function(value, name) { 17454 value = replaceVars(value, vars); 17455 17456 // Indexed array 17457 if (typeof(name) === 'number') { 17458 name = value; 17459 compare_node = 0; 17460 } 17461 17462 if (!compare_node || isEq(getStyle(compare_node, name), value)) 17463 dom.setStyle(node, name, ''); 17464 17465 stylesModified = 1; 17466 }); 17467 17468 // Remove style attribute if it's empty 17469 if (stylesModified && dom.getAttrib(node, 'style') == '') { 17470 node.removeAttribute('style'); 17471 node.removeAttribute('data-mce-style'); 17472 } 17473 17474 // Remove attributes 17475 each(format.attributes, function(value, name) { 17476 var valueOut; 17477 17478 value = replaceVars(value, vars); 17479 17480 // Indexed array 17481 if (typeof(name) === 'number') { 17482 name = value; 17483 compare_node = 0; 17484 } 17485 17486 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) { 17487 // Keep internal classes 17488 if (name == 'class') { 17489 value = dom.getAttrib(node, name); 17490 if (value) { 17491 // Build new class value where everything is removed except the internal prefixed classes 17492 valueOut = ''; 17493 each(value.split(/\s+/), function(cls) { 17494 if (/mce\w+/.test(cls)) 17495 valueOut += (valueOut ? ' ' : '') + cls; 17496 }); 17497 17498 // We got some internal classes left 17499 if (valueOut) { 17500 dom.setAttrib(node, name, valueOut); 17501 return; 17502 } 17503 } 17504 } 17505 17506 // IE6 has a bug where the attribute doesn't get removed correctly 17507 if (name == "class") 17508 node.removeAttribute('className'); 17509 17510 // Remove mce prefixed attributes 17511 if (MCE_ATTR_RE.test(name)) 17512 node.removeAttribute('data-mce-' + name); 17513 17514 node.removeAttribute(name); 17515 } 17516 }); 17517 17518 // Remove classes 17519 each(format.classes, function(value) { 17520 value = replaceVars(value, vars); 17521 17522 if (!compare_node || dom.hasClass(compare_node, value)) 17523 dom.removeClass(node, value); 17524 }); 17525 17526 // Check for non internal attributes 17527 attrs = dom.getAttribs(node); 17528 for (i = 0; i < attrs.length; i++) { 17529 if (attrs[i].nodeName.indexOf('_') !== 0) 17530 return FALSE; 17531 } 17532 } 17533 17534 // Remove the inline child if it's empty for example <b> or <span> 17535 if (format.remove != 'none') { 17536 removeNode(node, format); 17537 return TRUE; 17538 } 17539 }; 17540 17541 function removeNode(node, format) { 17542 var parentNode = node.parentNode, rootBlockElm; 17543 17544 function find(node, next, inc) { 17545 node = getNonWhiteSpaceSibling(node, next, inc); 17546 17547 return !node || (node.nodeName == 'BR' || isBlock(node)); 17548 }; 17549 17550 if (format.block) { 17551 if (!forcedRootBlock) { 17552 // Append BR elements if needed before we remove the block 17553 if (isBlock(node) && !isBlock(parentNode)) { 17554 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) 17555 node.insertBefore(dom.create('br'), node.firstChild); 17556 17557 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) 17558 node.appendChild(dom.create('br')); 17559 } 17560 } else { 17561 // Wrap the block in a forcedRootBlock if we are at the root of document 17562 if (parentNode == dom.getRoot()) { 17563 if (!format.list_block || !isEq(node, format.list_block)) { 17564 each(tinymce.grep(node.childNodes), function(node) { 17565 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 17566 if (!rootBlockElm) 17567 rootBlockElm = wrap(node, forcedRootBlock); 17568 else 17569 rootBlockElm.appendChild(node); 17570 } else 17571 rootBlockElm = 0; 17572 }); 17573 } 17574 } 17575 } 17576 } 17577 17578 // Never remove nodes that isn't the specified inline element if a selector is specified too 17579 if (format.selector && format.inline && !isEq(format.inline, node)) 17580 return; 17581 17582 dom.remove(node, 1); 17583 }; 17584 17585 function getNonWhiteSpaceSibling(node, next, inc) { 17586 if (node) { 17587 next = next ? 'nextSibling' : 'previousSibling'; 17588 17589 for (node = inc ? node : node[next]; node; node = node[next]) { 17590 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) 17591 return node; 17592 } 17593 } 17594 }; 17595 17596 function isBookmarkNode(node) { 17597 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; 17598 }; 17599 17600 function mergeSiblings(prev, next) { 17601 var marker, sibling, tmpSibling; 17602 17603 function compareElements(node1, node2) { 17604 // Not the same name 17605 if (node1.nodeName != node2.nodeName) 17606 return FALSE; 17607 17608 function getAttribs(node) { 17609 var attribs = {}; 17610 17611 each(dom.getAttribs(node), function(attr) { 17612 var name = attr.nodeName.toLowerCase(); 17613 17614 // Don't compare internal attributes or style 17615 if (name.indexOf('_') !== 0 && name !== 'style') 17616 attribs[name] = dom.getAttrib(node, name); 17617 }); 17618 17619 return attribs; 17620 }; 17621 17622 function compareObjects(obj1, obj2) { 17623 var value, name; 17624 17625 for (name in obj1) { 17626 // Obj1 has item obj2 doesn't have 17627 if (obj1.hasOwnProperty(name)) { 17628 value = obj2[name]; 17629 17630 // Obj2 doesn't have obj1 item 17631 if (value === undef) 17632 return FALSE; 17633 17634 // Obj2 item has a different value 17635 if (obj1[name] != value) 17636 return FALSE; 17637 17638 // Delete similar value 17639 delete obj2[name]; 17640 } 17641 } 17642 17643 // Check if obj 2 has something obj 1 doesn't have 17644 for (name in obj2) { 17645 // Obj2 has item obj1 doesn't have 17646 if (obj2.hasOwnProperty(name)) 17647 return FALSE; 17648 } 17649 17650 return TRUE; 17651 }; 17652 17653 // Attribs are not the same 17654 if (!compareObjects(getAttribs(node1), getAttribs(node2))) 17655 return FALSE; 17656 17657 // Styles are not the same 17658 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) 17659 return FALSE; 17660 17661 return TRUE; 17662 }; 17663 17664 function findElementSibling(node, sibling_name) { 17665 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 17666 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) 17667 return node; 17668 17669 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) 17670 return sibling; 17671 } 17672 17673 return node; 17674 }; 17675 17676 // Check if next/prev exists and that they are elements 17677 if (prev && next) { 17678 // If previous sibling is empty then jump over it 17679 prev = findElementSibling(prev, 'previousSibling'); 17680 next = findElementSibling(next, 'nextSibling'); 17681 17682 // Compare next and previous nodes 17683 if (compareElements(prev, next)) { 17684 // Append nodes between 17685 for (sibling = prev.nextSibling; sibling && sibling != next;) { 17686 tmpSibling = sibling; 17687 sibling = sibling.nextSibling; 17688 prev.appendChild(tmpSibling); 17689 } 17690 17691 // Remove next node 17692 dom.remove(next); 17693 17694 // Move children into prev node 17695 each(tinymce.grep(next.childNodes), function(node) { 17696 prev.appendChild(node); 17697 }); 17698 17699 return prev; 17700 } 17701 } 17702 17703 return next; 17704 }; 17705 17706 function isTextBlock(name) { 17707 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name); 17708 }; 17709 17710 function getContainer(rng, start) { 17711 var container, offset, lastIdx, walker; 17712 17713 container = rng[start ? 'startContainer' : 'endContainer']; 17714 offset = rng[start ? 'startOffset' : 'endOffset']; 17715 17716 if (container.nodeType == 1) { 17717 lastIdx = container.childNodes.length - 1; 17718 17719 if (!start && offset) 17720 offset--; 17721 17722 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 17723 } 17724 17725 // If start text node is excluded then walk to the next node 17726 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 17727 container = new TreeWalker(container, ed.getBody()).next() || container; 17728 } 17729 17730 // If end text node is excluded then walk to the previous node 17731 if (container.nodeType === 3 && !start && offset === 0) { 17732 container = new TreeWalker(container, ed.getBody()).prev() || container; 17733 } 17734 17735 return container; 17736 }; 17737 17738 function performCaretAction(type, name, vars) { 17739 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 17740 17741 // Creates a caret container bogus element 17742 function createCaretContainer(fill) { 17743 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 17744 17745 if (fill) { 17746 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 17747 } 17748 17749 return caretContainer; 17750 }; 17751 17752 function isCaretContainerEmpty(node, nodes) { 17753 while (node) { 17754 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 17755 return false; 17756 } 17757 17758 // Collect nodes 17759 if (nodes && node.nodeType === 1) { 17760 nodes.push(node); 17761 } 17762 17763 node = node.firstChild; 17764 } 17765 17766 return true; 17767 }; 17768 17769 // Returns any parent caret container element 17770 function getParentCaretContainer(node) { 17771 while (node) { 17772 if (node.id === caretContainerId) { 17773 return node; 17774 } 17775 17776 node = node.parentNode; 17777 } 17778 }; 17779 17780 // Finds the first text node in the specified node 17781 function findFirstTextNode(node) { 17782 var walker; 17783 17784 if (node) { 17785 walker = new TreeWalker(node, node); 17786 17787 for (node = walker.current(); node; node = walker.next()) { 17788 if (node.nodeType === 3) { 17789 return node; 17790 } 17791 } 17792 } 17793 }; 17794 17795 // Removes the caret container for the specified node or all on the current document 17796 function removeCaretContainer(node, move_caret) { 17797 var child, rng; 17798 17799 if (!node) { 17800 node = getParentCaretContainer(selection.getStart()); 17801 17802 if (!node) { 17803 while (node = dom.get(caretContainerId)) { 17804 removeCaretContainer(node, false); 17805 } 17806 } 17807 } else { 17808 rng = selection.getRng(true); 17809 17810 if (isCaretContainerEmpty(node)) { 17811 if (move_caret !== false) { 17812 rng.setStartBefore(node); 17813 rng.setEndBefore(node); 17814 } 17815 17816 dom.remove(node); 17817 } else { 17818 child = findFirstTextNode(node); 17819 17820 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 17821 child = child.deleteData(0, 1); 17822 } 17823 17824 dom.remove(node, 1); 17825 } 17826 17827 selection.setRng(rng); 17828 } 17829 }; 17830 17831 // Applies formatting to the caret postion 17832 function applyCaretFormat() { 17833 var rng, caretContainer, textNode, offset, bookmark, container, text; 17834 17835 rng = selection.getRng(true); 17836 offset = rng.startOffset; 17837 container = rng.startContainer; 17838 text = container.nodeValue; 17839 17840 caretContainer = getParentCaretContainer(selection.getStart()); 17841 if (caretContainer) { 17842 textNode = findFirstTextNode(caretContainer); 17843 } 17844 17845 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 17846 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 17847 // Get bookmark of caret position 17848 bookmark = selection.getBookmark(); 17849 17850 // Collapse bookmark range (WebKit) 17851 rng.collapse(true); 17852 17853 // Expand the range to the closest word and split it at those points 17854 rng = expandRng(rng, get(name)); 17855 rng = rangeUtils.split(rng); 17856 17857 // Apply the format to the range 17858 apply(name, vars, rng); 17859 17860 // Move selection back to caret position 17861 selection.moveToBookmark(bookmark); 17862 } else { 17863 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 17864 caretContainer = createCaretContainer(true); 17865 textNode = caretContainer.firstChild; 17866 17867 rng.insertNode(caretContainer); 17868 offset = 1; 17869 17870 apply(name, vars, caretContainer); 17871 } else { 17872 apply(name, vars, caretContainer); 17873 } 17874 17875 // Move selection to text node 17876 selection.setCursorLocation(textNode, offset); 17877 } 17878 }; 17879 17880 function removeCaretFormat() { 17881 var rng = selection.getRng(true), container, offset, bookmark, 17882 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 17883 17884 container = rng.startContainer; 17885 offset = rng.startOffset; 17886 node = container; 17887 17888 if (container.nodeType == 3) { 17889 if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) { 17890 hasContentAfter = true; 17891 } 17892 17893 node = node.parentNode; 17894 } 17895 17896 while (node) { 17897 if (matchNode(node, name, vars)) { 17898 formatNode = node; 17899 break; 17900 } 17901 17902 if (node.nextSibling) { 17903 hasContentAfter = true; 17904 } 17905 17906 parents.push(node); 17907 node = node.parentNode; 17908 } 17909 17910 // Node doesn't have the specified format 17911 if (!formatNode) { 17912 return; 17913 } 17914 17915 // Is there contents after the caret then remove the format on the element 17916 if (hasContentAfter) { 17917 // Get bookmark of caret position 17918 bookmark = selection.getBookmark(); 17919 17920 // Collapse bookmark range (WebKit) 17921 rng.collapse(true); 17922 17923 // Expand the range to the closest word and split it at those points 17924 rng = expandRng(rng, get(name), true); 17925 rng = rangeUtils.split(rng); 17926 17927 // Remove the format from the range 17928 remove(name, vars, rng); 17929 17930 // Move selection back to caret position 17931 selection.moveToBookmark(bookmark); 17932 } else { 17933 caretContainer = createCaretContainer(); 17934 17935 node = caretContainer; 17936 for (i = parents.length - 1; i >= 0; i--) { 17937 node.appendChild(dom.clone(parents[i], false)); 17938 node = node.firstChild; 17939 } 17940 17941 // Insert invisible character into inner most format element 17942 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 17943 node = node.firstChild; 17944 17945 // Insert caret container after the formated node 17946 dom.insertAfter(caretContainer, formatNode); 17947 17948 // Move selection to text node 17949 selection.setCursorLocation(node, 1); 17950 } 17951 }; 17952 17953 // Checks if the parent caret container node isn't empty if that is the case it 17954 // will remove the bogus state on all children that isn't empty 17955 function unmarkBogusCaretParents() { 17956 var i, caretContainer, node; 17957 17958 caretContainer = getParentCaretContainer(selection.getStart()); 17959 if (caretContainer && !dom.isEmpty(caretContainer)) { 17960 tinymce.walk(caretContainer, function(node) { 17961 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 17962 dom.setAttrib(node, 'data-mce-bogus', null); 17963 } 17964 }, 'childNodes'); 17965 } 17966 }; 17967 17968 // Only bind the caret events once 17969 if (!self._hasCaretEvents) { 17970 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 17971 ed.onBeforeGetContent.addToTop(function() { 17972 var nodes = [], i; 17973 17974 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 17975 // Mark children 17976 i = nodes.length; 17977 while (i--) { 17978 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 17979 } 17980 } 17981 }); 17982 17983 // Remove caret container on mouse up and on key up 17984 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) { 17985 ed[name].addToTop(function() { 17986 removeCaretContainer(); 17987 unmarkBogusCaretParents(); 17988 }); 17989 }); 17990 17991 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys 17992 ed.onKeyDown.addToTop(function(ed, e) { 17993 var keyCode = e.keyCode; 17994 17995 if (keyCode == 8 || keyCode == 37 || keyCode == 39) { 17996 removeCaretContainer(getParentCaretContainer(selection.getStart())); 17997 } 17998 17999 unmarkBogusCaretParents(); 18000 }); 18001 18002 // Remove bogus state if they got filled by contents using editor.selection.setContent 18003 selection.onSetContent.add(unmarkBogusCaretParents); 18004 18005 self._hasCaretEvents = true; 18006 } 18007 18008 // Do apply or remove caret format 18009 if (type == "apply") { 18010 applyCaretFormat(); 18011 } else { 18012 removeCaretFormat(); 18013 } 18014 }; 18015 18016 function moveStart(rng) { 18017 var container = rng.startContainer, 18018 offset = rng.startOffset, isAtEndOfText, 18019 walker, node, nodes, tmpNode; 18020 18021 // Convert text node into index if possible 18022 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 18023 // Get the parent container location and walk from there 18024 offset = nodeIndex(container); 18025 container = container.parentNode; 18026 isAtEndOfText = true; 18027 } 18028 18029 // Move startContainer/startOffset in to a suitable node 18030 if (container.nodeType == 1) { 18031 nodes = container.childNodes; 18032 container = nodes[Math.min(offset, nodes.length - 1)]; 18033 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 18034 18035 // If offset is at end of the parent node walk to the next one 18036 if (offset > nodes.length - 1 || isAtEndOfText) 18037 walker.next(); 18038 18039 for (node = walker.current(); node; node = walker.next()) { 18040 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 18041 // IE has a "neat" feature where it moves the start node into the closest element 18042 // we can avoid this by inserting an element before it and then remove it after we set the selection 18043 tmpNode = dom.create('a', null, INVISIBLE_CHAR); 18044 node.parentNode.insertBefore(tmpNode, node); 18045 18046 // Set selection and remove tmpNode 18047 rng.setStart(node, 0); 18048 selection.setRng(rng); 18049 dom.remove(tmpNode); 18050 18051 return; 18052 } 18053 } 18054 } 18055 }; 18056 }; 18057 })(tinymce); 18058 18059 tinymce.onAddEditor.add(function(tinymce, ed) { 18060 var filters, fontSizes, dom, settings = ed.settings; 18061 18062 function replaceWithSpan(node, styles) { 18063 tinymce.each(styles, function(value, name) { 18064 if (value) 18065 dom.setStyle(node, name, value); 18066 }); 18067 18068 dom.rename(node, 'span'); 18069 }; 18070 18071 function convert(editor, params) { 18072 dom = editor.dom; 18073 18074 if (settings.convert_fonts_to_spans) { 18075 tinymce.each(dom.select('font,u,strike', params.node), function(node) { 18076 filters[node.nodeName.toLowerCase()](ed.dom, node); 18077 }); 18078 } 18079 }; 18080 18081 if (settings.inline_styles) { 18082 fontSizes = tinymce.explode(settings.font_size_legacy_values); 18083 18084 filters = { 18085 font : function(dom, node) { 18086 replaceWithSpan(node, { 18087 backgroundColor : node.style.backgroundColor, 18088 color : node.color, 18089 fontFamily : node.face, 18090 fontSize : fontSizes[parseInt(node.size, 10) - 1] 18091 }); 18092 }, 18093 18094 u : function(dom, node) { 18095 replaceWithSpan(node, { 18096 textDecoration : 'underline' 18097 }); 18098 }, 18099 18100 strike : function(dom, node) { 18101 replaceWithSpan(node, { 18102 textDecoration : 'line-through' 18103 }); 18104 } 18105 }; 18106 18107 ed.onPreProcess.add(convert); 18108 ed.onSetContent.add(convert); 18109 18110 ed.onInit.add(function() { 18111 ed.selection.onSetContent.add(convert); 18112 }); 18113 } 18114 }); 18115 18116 (function(tinymce) { 18117 var TreeWalker = tinymce.dom.TreeWalker; 18118 18119 tinymce.EnterKey = function(editor) { 18120 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 18121 18122 function handleEnterKey(evt) { 18123 var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, 18124 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 18125 18126 // Returns true if the block can be split into two blocks or not 18127 function canSplitBlock(node) { 18128 return node && 18129 dom.isBlock(node) && 18130 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 18131 !/^(fixed|absolute)/i.test(node.style.position) && 18132 dom.getContentEditable(node) !== "true"; 18133 }; 18134 18135 // Renders empty block on IE 18136 function renderBlockOnIE(block) { 18137 var oldRng; 18138 18139 if (tinymce.isIE && dom.isBlock(block)) { 18140 oldRng = selection.getRng(); 18141 block.appendChild(dom.create('span', null, '\u00a0')); 18142 selection.select(block); 18143 block.lastChild.outerHTML = ''; 18144 selection.setRng(oldRng); 18145 } 18146 }; 18147 18148 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p> 18149 function trimInlineElementsOnLeftSideOfBlock(block) { 18150 var node = block, firstChilds = [], i; 18151 18152 // Find inner most first child ex: <p><i><b>*</b></i></p> 18153 while (node = node.firstChild) { 18154 if (dom.isBlock(node)) { 18155 return; 18156 } 18157 18158 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18159 firstChilds.push(node); 18160 } 18161 } 18162 18163 i = firstChilds.length; 18164 while (i--) { 18165 node = firstChilds[i]; 18166 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 18167 dom.remove(node); 18168 } 18169 } 18170 }; 18171 18172 // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image 18173 function moveToCaretPosition(root) { 18174 var walker, node, rng, y, viewPort, lastNode = root, tempElm; 18175 18176 rng = dom.createRng(); 18177 18178 if (root.hasChildNodes()) { 18179 walker = new TreeWalker(root, root); 18180 18181 while (node = walker.current()) { 18182 if (node.nodeType == 3) { 18183 rng.setStart(node, 0); 18184 rng.setEnd(node, 0); 18185 break; 18186 } 18187 18188 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 18189 rng.setStartBefore(node); 18190 rng.setEndBefore(node); 18191 break; 18192 } 18193 18194 lastNode = node; 18195 node = walker.next(); 18196 } 18197 18198 if (!node) { 18199 rng.setStart(lastNode, 0); 18200 rng.setEnd(lastNode, 0); 18201 } 18202 } else { 18203 if (root.nodeName == 'BR') { 18204 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 18205 // Trick on older IE versions to render the caret before the BR between two lists 18206 if (!documentMode || documentMode < 9) { 18207 tempElm = dom.create('br'); 18208 root.parentNode.insertBefore(tempElm, root); 18209 } 18210 18211 rng.setStartBefore(root); 18212 rng.setEndBefore(root); 18213 } else { 18214 rng.setStartAfter(root); 18215 rng.setEndAfter(root); 18216 } 18217 } else { 18218 rng.setStart(root, 0); 18219 rng.setEnd(root, 0); 18220 } 18221 } 18222 18223 selection.setRng(rng); 18224 18225 // Remove tempElm created for old IE:s 18226 dom.remove(tempElm); 18227 18228 viewPort = dom.getViewPort(editor.getWin()); 18229 18230 // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs 18231 y = dom.getPos(root).y; 18232 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) { 18233 editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks 18234 } 18235 }; 18236 18237 // Creates a new block element by cloning the current one or creating a new one if the name is specified 18238 // This function will also copy any text formatting from the parent block and add it to the new one 18239 function createNewBlock(name) { 18240 var node = container, block, clonedNode, caretNode; 18241 18242 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false); 18243 caretNode = block; 18244 18245 // Clone any parent styles 18246 if (settings.keep_styles !== false) { 18247 do { 18248 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { 18249 clonedNode = node.cloneNode(false); 18250 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 18251 18252 if (block.hasChildNodes()) { 18253 clonedNode.appendChild(block.firstChild); 18254 block.appendChild(clonedNode); 18255 } else { 18256 caretNode = clonedNode; 18257 block.appendChild(clonedNode); 18258 } 18259 } 18260 } while (node = node.parentNode); 18261 } 18262 18263 // BR is needed in empty blocks on non IE browsers 18264 if (!tinymce.isIE) { 18265 caretNode.innerHTML = '<br>'; 18266 } 18267 18268 return block; 18269 }; 18270 18271 // Returns true/false if the caret is at the start/end of the parent block element 18272 function isCaretAtStartOrEndOfBlock(start) { 18273 var walker, node, name; 18274 18275 // Caret is in the middle of a text node like "a|b" 18276 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 18277 return false; 18278 } 18279 18280 // If after the last element in block node edge case for #5091 18281 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 18282 return true; 18283 } 18284 18285 // If the caret if before the first element in parentBlock 18286 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 18287 return true; 18288 } 18289 18290 // Caret can be before/after a table 18291 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 18292 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 18293 } 18294 18295 // Walk the DOM and look for text nodes or non empty elements 18296 walker = new TreeWalker(container, parentBlock); 18297 18298 // If caret is in beginning or end of a text block then jump to the next/previous node 18299 if (container.nodeType == 3) { 18300 if (start && offset == 0) { 18301 walker.prev(); 18302 } else if (!start && offset == container.nodeValue.length) { 18303 walker.next(); 18304 } 18305 } 18306 18307 while (node = walker.current()) { 18308 if (node.nodeType === 1) { 18309 // Ignore bogus elements 18310 if (!node.getAttribute('data-mce-bogus')) { 18311 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 18312 name = node.nodeName.toLowerCase(); 18313 if (nonEmptyElementsMap[name] && name !== 'br') { 18314 return false; 18315 } 18316 } 18317 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 18318 return false; 18319 } 18320 18321 if (start) { 18322 walker.prev(); 18323 } else { 18324 walker.next(); 18325 } 18326 } 18327 18328 return true; 18329 }; 18330 18331 // Wraps any text nodes or inline elements in the specified forced root block name 18332 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 18333 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P'; 18334 18335 // Not in a block element or in a table cell or caption 18336 parentBlock = dom.getParent(container, dom.isBlock); 18337 if (!parentBlock || !canSplitBlock(parentBlock)) { 18338 parentBlock = parentBlock || editableRoot; 18339 18340 if (!parentBlock.hasChildNodes()) { 18341 newBlock = dom.create(blockName); 18342 parentBlock.appendChild(newBlock); 18343 rng.setStart(newBlock, 0); 18344 rng.setEnd(newBlock, 0); 18345 return newBlock; 18346 } 18347 18348 // Find parent that is the first child of parentBlock 18349 node = container; 18350 while (node.parentNode != parentBlock) { 18351 node = node.parentNode; 18352 } 18353 18354 // Loop left to find start node start wrapping at 18355 while (node && !dom.isBlock(node)) { 18356 startNode = node; 18357 node = node.previousSibling; 18358 } 18359 18360 if (startNode) { 18361 newBlock = dom.create(blockName); 18362 startNode.parentNode.insertBefore(newBlock, startNode); 18363 18364 // Start wrapping until we hit a block 18365 node = startNode; 18366 while (node && !dom.isBlock(node)) { 18367 next = node.nextSibling; 18368 newBlock.appendChild(node); 18369 node = next; 18370 } 18371 18372 // Restore range to it's past location 18373 rng.setStart(container, offset); 18374 rng.setEnd(container, offset); 18375 } 18376 } 18377 18378 return container; 18379 }; 18380 18381 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 18382 function handleEmptyListItem() { 18383 function isFirstOrLastLi(first) { 18384 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 18385 18386 // Find first/last element since there might be whitespace there 18387 while (node) { 18388 if (node.nodeType == 1) { 18389 break; 18390 } 18391 18392 node = node[first ? 'nextSibling' : 'previousSibling']; 18393 } 18394 18395 return node === parentBlock; 18396 }; 18397 18398 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 18399 18400 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 18401 // Is first and last list item then replace the OL/UL with a text block 18402 dom.replace(newBlock, containerBlock); 18403 } else if (isFirstOrLastLi(true)) { 18404 // First LI in list then remove LI and add text block before list 18405 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 18406 } else if (isFirstOrLastLi()) { 18407 // Last LI in list then temove LI and add text block after list 18408 dom.insertAfter(newBlock, containerBlock); 18409 renderBlockOnIE(newBlock); 18410 } else { 18411 // Middle LI in list the split the list and insert a text block in the middle 18412 // Extract after fragment and insert it after the current block 18413 tmpRng = rng.cloneRange(); 18414 tmpRng.setStartAfter(parentBlock); 18415 tmpRng.setEndAfter(containerBlock); 18416 fragment = tmpRng.extractContents(); 18417 dom.insertAfter(fragment, containerBlock); 18418 dom.insertAfter(newBlock, containerBlock); 18419 } 18420 18421 dom.remove(parentBlock); 18422 moveToCaretPosition(newBlock); 18423 undoManager.add(); 18424 }; 18425 18426 // Walks the parent block to the right and look for BR elements 18427 function hasRightSideBr() { 18428 var walker = new TreeWalker(container, parentBlock), node; 18429 18430 while (node = walker.current()) { 18431 if (node.nodeName == 'BR') { 18432 return true; 18433 } 18434 18435 node = walker.next(); 18436 } 18437 } 18438 18439 // Inserts a BR element if the forced_root_block option is set to false or empty string 18440 function insertBr() { 18441 var brElm, extraBr; 18442 18443 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 18444 // Insert extra BR element at the end block elements 18445 if (!tinymce.isIE && !hasRightSideBr()) { 18446 brElm = dom.create('br') 18447 rng.insertNode(brElm); 18448 rng.setStartAfter(brElm); 18449 rng.setEndAfter(brElm); 18450 extraBr = true; 18451 } 18452 } 18453 18454 brElm = dom.create('br'); 18455 rng.insertNode(brElm); 18456 18457 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 18458 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 18459 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 18460 } 18461 18462 if (!extraBr) { 18463 rng.setStartAfter(brElm); 18464 rng.setEndAfter(brElm); 18465 } else { 18466 rng.setStartBefore(brElm); 18467 rng.setEndBefore(brElm); 18468 } 18469 18470 selection.setRng(rng); 18471 undoManager.add(); 18472 }; 18473 18474 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 18475 function trimLeadingLineBreaks(node) { 18476 do { 18477 if (node.nodeType === 3) { 18478 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 18479 } 18480 18481 node = node.firstChild; 18482 } while (node); 18483 }; 18484 18485 function getEditableRoot(node) { 18486 var root = dom.getRoot(), parent, editableRoot; 18487 18488 // Get all parents until we hit a non editable parent or the root 18489 parent = node; 18490 while (parent !== root && dom.getContentEditable(parent) !== "false") { 18491 if (dom.getContentEditable(parent) === "true") { 18492 editableRoot = parent; 18493 } 18494 18495 parent = parent.parentNode; 18496 } 18497 18498 return parent !== root ? editableRoot : root; 18499 }; 18500 18501 // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block 18502 function addBrToBlockIfNeeded(block) { 18503 var lastChild; 18504 18505 // IE will render the blocks correctly other browsers needs a BR 18506 if (!tinymce.isIE) { 18507 block.normalize(); // Remove empty text nodes that got left behind by the extract 18508 18509 // Check if the block is empty or contains a floated last child 18510 lastChild = block.lastChild; 18511 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 18512 dom.add(block, 'br'); 18513 } 18514 } 18515 }; 18516 18517 // Delete any selected contents 18518 if (!rng.collapsed) { 18519 editor.execCommand('Delete'); 18520 return; 18521 } 18522 18523 // Event is blocked by some other handler for example the lists plugin 18524 if (evt.isDefaultPrevented()) { 18525 return; 18526 } 18527 18528 // Setup range items and newBlockName 18529 container = rng.startContainer; 18530 offset = rng.startOffset; 18531 newBlockName = settings.forced_root_block; 18532 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 18533 documentMode = dom.doc.documentMode; 18534 18535 // Resolve node index 18536 if (container.nodeType == 1 && container.hasChildNodes()) { 18537 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 18538 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 18539 if (isAfterLastNodeInContainer && container.nodeType == 3) { 18540 offset = container.nodeValue.length; 18541 } else { 18542 offset = 0; 18543 } 18544 } 18545 18546 // Get editable root node normaly the body element but sometimes a div or span 18547 editableRoot = getEditableRoot(container); 18548 18549 // If there is no editable root then enter is done inside a contentEditable false element 18550 if (!editableRoot) { 18551 return; 18552 } 18553 18554 undoManager.beforeChange(); 18555 18556 // If editable root isn't block nor the root of the editor 18557 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 18558 if (!newBlockName || evt.shiftKey) { 18559 insertBr(); 18560 } 18561 18562 return; 18563 } 18564 18565 // Wrap the current node and it's sibling in a default block if it's needed. 18566 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 18567 // This won't happen if root blocks are disabled or the shiftKey is pressed 18568 if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) { 18569 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 18570 } 18571 18572 // Find parent block and setup empty block paddings 18573 parentBlock = dom.getParent(container, dom.isBlock); 18574 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 18575 18576 // Setup block names 18577 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18578 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 18579 18580 // Handle enter inside an empty list item 18581 if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) { 18582 // Let the list plugin or browser handle nested lists for now 18583 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) { 18584 return false; 18585 } 18586 18587 handleEmptyListItem(); 18588 return; 18589 } 18590 18591 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 18592 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 18593 if (!evt.shiftKey) { 18594 insertBr(); 18595 return; 18596 } 18597 } else { 18598 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 18599 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) { 18600 insertBr(); 18601 return; 18602 } 18603 } 18604 18605 // Default block name if it's not configured 18606 newBlockName = newBlockName || 'P'; 18607 18608 // Insert new block before/after the parent block depending on caret location 18609 if (isCaretAtStartOrEndOfBlock()) { 18610 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup 18611 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 18612 newBlock = createNewBlock(newBlockName); 18613 } else { 18614 newBlock = createNewBlock(); 18615 } 18616 18617 // Split the current container block element if enter is pressed inside an empty inner block element 18618 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 18619 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 18620 newBlock = dom.split(containerBlock, parentBlock); 18621 } else { 18622 dom.insertAfter(newBlock, parentBlock); 18623 } 18624 18625 moveToCaretPosition(newBlock); 18626 } else if (isCaretAtStartOrEndOfBlock(true)) { 18627 // Insert new block before 18628 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 18629 renderBlockOnIE(newBlock); 18630 } else { 18631 // Extract after fragment and insert it after the current block 18632 tmpRng = rng.cloneRange(); 18633 tmpRng.setEndAfter(parentBlock); 18634 fragment = tmpRng.extractContents(); 18635 trimLeadingLineBreaks(fragment); 18636 newBlock = fragment.firstChild; 18637 dom.insertAfter(fragment, parentBlock); 18638 trimInlineElementsOnLeftSideOfBlock(newBlock); 18639 addBrToBlockIfNeeded(parentBlock); 18640 moveToCaretPosition(newBlock); 18641 } 18642 18643 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 18644 undoManager.add(); 18645 } 18646 18647 editor.onKeyDown.add(function(ed, evt) { 18648 if (evt.keyCode == 13) { 18649 if (handleEnterKey(evt) !== false) { 18650 evt.preventDefault(); 18651 } 18652 } 18653 }); 18654 }; 18655 })(tinymce); 18656 18657